diff options
Diffstat (limited to 'openecomp-ui')
425 files changed, 39484 insertions, 0 deletions
diff --git a/openecomp-ui/.babelrc b/openecomp-ui/.babelrc new file mode 100644 index 0000000000..635081d464 --- /dev/null +++ b/openecomp-ui/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": ["stage-0", "react"], + "plugins": [ + "transform-es2015-modules-commonjs", + "transform-es2015-destructuring", + "transform-es2015-spread", + "transform-object-rest-spread", + "transform-class-properties" + ] +} diff --git a/openecomp-ui/.editorconfig b/openecomp-ui/.editorconfig new file mode 100644 index 0000000000..fcdada6db0 --- /dev/null +++ b/openecomp-ui/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# tab indentation +[src/**.js] +[src/**.jsx] +indent_style = tab + + diff --git a/openecomp-ui/.eslintignore b/openecomp-ui/.eslintignore new file mode 100644 index 0000000000..bd11f6ec26 --- /dev/null +++ b/openecomp-ui/.eslintignore @@ -0,0 +1,4 @@ +webpack.config*.js +gulpfile.js +fixture +tools diff --git a/openecomp-ui/.eslintrc b/openecomp-ui/.eslintrc new file mode 100644 index 0000000000..da8a422df3 --- /dev/null +++ b/openecomp-ui/.eslintrc @@ -0,0 +1,155 @@ +{ + "parser": "babel-eslint", + "env": { + "es6": true, + "jquery": true, + "node": true, + "mocha": true + }, + "plugins": [ + "react", + "import" + ], + "ecmaFeatures": { + "jsx": true, + "classes": true, + "modules": true + }, + "globals": { + "Event": true, + "window": true, + "navigator": true, + "System": true, + "document": true, + "localStorage": true, + "sessionStorage": true, + "Image": true, + "requestAnimationFrame": true, + "cancelAnimationFrame": true, + "DEBUG": true, + "SVGElement": true, + "FormData": true, + "DEV": true, + "Blob": true, + "XMLHttpRequest": true, + "URL": true, + "PunchOutRegistry": true, + "it": true, + "describe": true + }, + "rules": { + "linebreak-style": 0, + "no-unused-vars": 2, + "no-bitwise": 0, + "no-eq-null": 2, + "eqeqeq": 2, + "wrap-iife": [ + 2, + "any" + ], + "no-unused-expressions": 2, + "indent": [ + 1, + "tab", + { + "SwitchCase": 1 + } + ], + "no-use-before-define": 2, + "new-cap": [ + 2, + { + "capIsNewExceptions": [ + "DataTable", + "V" + ] + } + ], + "no-caller": 2, + "no-empty": 2, + "no-undef": 2, + "quotes": [ + 2, + "single", + "avoid-escape" + ], + "jsx-quotes": [ + 2, + "prefer-single" + ], + "no-plusplus": 0, + "no-cond-assign": [ + 2, + "except-parens" + ], + "comma-style": [ + 2, + "last" + ], + "no-invalid-this": 0, + "dot-notation": 0, + "max-len": [ + 1, + 200 + ], + "camelcase": [ + 2, + { + "properties": "never" + } + ], + "curly": 2, + "brace-style": 0, + "semi": [ + 2, + "always" + ], + "space-in-brackets": [ + 0, + "never" + ], + "space-infix-ops": 2, + "import/default": 0, + "import/no-unresolved": 0, + "import/no-named-as-default": 2, + "import/no-duplicates": 0, + "import/imports-first": 2, + "import/export": 2, + "react/display-name": 0, + "react/forbid-prop-types": 0, + "react/jsx-boolean-value": 0, + "react/jsx-closing-bracket-location": [ + 1, + { + "nonEmpty": "after-props", + "selfClosing": "after-props" + } + ], + "react/jsx-curly-spacing": 0, + "react/jsx-indent-props": [ + 1, + "tab" + ], + "react/jsx-max-props-per-line": 0, + "react/jsx-no-duplicate-props": 1, + "react/jsx-no-literals": 0, + "react/jsx-no-undef": 1, + "react/jsx-sort-prop-types": 0, + "react/jsx-sort-props": 0, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/no-danger": 1, + "react/no-did-mount-set-state": 2, + "react/no-did-update-set-state": 2, + "react/no-direct-mutation-state": 1, + "react/no-multi-comp": 0, + "react/no-set-state": 0, + "react/no-unknown-property": 1, + "react/prop-types": 0, + "react/react-in-jsx-scope": 1, + "react/require-extension": 1, + "react/self-closing-comp": 1, + "react/sort-comp": 0, + "react/wrap-multilines": 1 + } +} diff --git a/openecomp-ui/.gitignore b/openecomp-ui/.gitignore new file mode 100644 index 0000000000..cce0a545f7 --- /dev/null +++ b/openecomp-ui/.gitignore @@ -0,0 +1,7 @@ + +.idea + +dist +node_modules +devConfig.json +.npmrc diff --git a/openecomp-ui/LICENSE b/openecomp-ui/LICENSE new file mode 100644 index 0000000000..2507db8a57 --- /dev/null +++ b/openecomp-ui/LICENSE @@ -0,0 +1 @@ +(c) Copyright 2016 ECOMP, all rights reserved. diff --git a/openecomp-ui/devConfig.defaults.json b/openecomp-ui/devConfig.defaults.json new file mode 100644 index 0000000000..bfcf9aae8f --- /dev/null +++ b/openecomp-ui/devConfig.defaults.json @@ -0,0 +1,6 @@ +{ + "port": 9000, + "proxyATTTarget": null, + "proxyTarget": null, + "useFixture": false +} diff --git a/openecomp-ui/fixture/data/entitlementPools.json b/openecomp-ui/fixture/data/entitlementPools.json new file mode 100644 index 0000000000..22750cbb6d --- /dev/null +++ b/openecomp-ui/fixture/data/entitlementPools.json @@ -0,0 +1,36 @@ +{ + "results":[ + { + "name": "ep1", + "description": "string", + "thresholdValue": 75, + "thresholdUnits": "%", + "entitlementMetric": {"choice": "User", "other":""}, + "increments": "string", + "aggregationFunction": {"choice": "Average", "other": ""}, + "operationalScope": {"choices": ["VM"], "other": ""}, + "time": {"choice": "Hour", "other": ""}, + "sku": "DEF2-385A-4521-AAAA", + "id": "1", + "referencingFeatureGroups": ["1","2"], + "partNumber": "51529" + }, + { + "name": "ep2", + "description": "string", + "thresholdValue": 99, + "thresholdUnits": "%", + "entitlementMetric": {"choice": "User", "other":""}, + "increments": "string", + "aggregationFunction": {"choice": "Average", "other": ""}, + "operationalScope": {"choices": ["Other"], "other": "blabla"}, + "time": {"choice": "Hour", "other": ""}, + "sku": "DEF2-385A-4521-AAAA", + "id": "2", + "refCount": 0, + "partNumber": "51529", + "referencingFeatureGroups": [] + } + ] +} + diff --git a/openecomp-ui/fixture/data/featureGroup.json b/openecomp-ui/fixture/data/featureGroup.json new file mode 100644 index 0000000000..278baecb36 --- /dev/null +++ b/openecomp-ui/fixture/data/featureGroup.json @@ -0,0 +1,59 @@ +{ + "name": "fssss", + "description": "gdfgdfgdsfg", + "id": "1", + "licenseKeyGroupsIds": [ + "1,2" + ], + "entitlementPoolsIds": [ + "1,2" + ], + "licenseKeyGroups": [ + { + "name": "ls1", + "description": "string", + "type": "string", + "operationalScope": "string", + "id": "1", + "refCount": 0 + }, + { + "name": "ls2", + "description": "string", + "type": "string", + "operationalScope": "string", + "id": "1", + "refCount": 0 + } + ], + "entitlementPools": [ + { + "name": "ep1", + "description": "string", + "thresholdValue": 0, + "thresholdUnits": "string", + "entitlementMetric": "string", + "increments": "string", + "aggregationFunction": "string", + "operationalScope": "string", + "time": "string", + "sku": "string", + "id": "string", + "refCount": 0 + }, + { + "name": "ep2", + "description": "string", + "thresholdValue": 0, + "thresholdUnits": "string", + "entitlementMetric": "string", + "increments": "string", + "aggregationFunction": "string", + "operationalScope": "string", + "time": "string", + "sku": "string", + "id": "string", + "refCount": 0 + } + ] +} diff --git a/openecomp-ui/fixture/data/featureGroups.json b/openecomp-ui/fixture/data/featureGroups.json new file mode 100644 index 0000000000..eea4967ca8 --- /dev/null +++ b/openecomp-ui/fixture/data/featureGroups.json @@ -0,0 +1,28 @@ +{ + "results": [ + { + "name": "fs1", + "id": "0", + "description": "fs1-description", + "licenseKeyGroupsIds": [ + "1" + ], + "entitlementPoolsIds": [ + "1" + ], + "referencingLicenseAgreements": ["1","2"] + }, + { + "name": "fs2", + "id": "1", + "description": "fs2-description", + "licenseKeyGroupsIds": [ + "2" + ], + "entitlementPoolsIds": [ + "2" + ], + "referencingLicenseAgreements": [] + } + ] +} diff --git a/openecomp-ui/fixture/data/licenseAgreementList.json b/openecomp-ui/fixture/data/licenseAgreementList.json new file mode 100644 index 0000000000..b113295fcb --- /dev/null +++ b/openecomp-ui/fixture/data/licenseAgreementList.json @@ -0,0 +1,33 @@ +{ + "results": [ + { + "name": "name0", + "description": "description0", + "licenseTerm": {"choice": "Other", "other": "blabla"}, + "requirementsAndConstrains": "requirementsAndConstrains0", + "featureGroupsIds": [], + "id": "0" + }, + { + "name": "name1", + "description": "description1", + "licenseTerm": {"choice": "Fixed_Term", "other": ""}, + "requirementsAndConstrains": "requirementsAndConstrains1", + "featureGroupsIds": [ + "1" + ], + "id": "1" + }, + { + "name": "name2", + "description": "description2", + "licenseTerm": {"choice": "Unlimited", "other": ""}, + "requirementsAndConstrains": "requirementsAndConstrains2", + "featureGroupsIds": [ + "2" + ], + "id": "2" + } + ], + "listCount": 3 +} diff --git a/openecomp-ui/fixture/data/licenseKeyGroups.json b/openecomp-ui/fixture/data/licenseKeyGroups.json new file mode 100644 index 0000000000..74050ab033 --- /dev/null +++ b/openecomp-ui/fixture/data/licenseKeyGroups.json @@ -0,0 +1,20 @@ +{ + "results":[ + { + "name": "lsk1", + "description": "string", + "type": "Unique", + "operationalScope": {"choices": ["Network_Wide","VM"], "other": ""}, + "id": "1", + "referencingFeatureGroups":["1","2"] + }, + { + "name": "lsk2", + "description": "string", + "type": "One_Time", + "operationalScope": {"choices": ["Other"], "other": "blabla"}, + "id": "2", + "referencingFeatureGroups": 0 + } + ] +} diff --git a/openecomp-ui/fixture/data/licenseModels.json b/openecomp-ui/fixture/data/licenseModels.json new file mode 100644 index 0000000000..5239c4fafc --- /dev/null +++ b/openecomp-ui/fixture/data/licenseModels.json @@ -0,0 +1,16 @@ +{ + "results":[ + { + "vendorName": "Omer Corp.", + "description": "", + "iconRef": "string", + "id": "1" + }, + { + "vendorName": "Robotricks", + "description": "Optimus Prime", + "iconRef": "string", + "id": "2" + } + ] +} diff --git a/openecomp-ui/fixture/data/softwareProduct.json b/openecomp-ui/fixture/data/softwareProduct.json new file mode 100644 index 0000000000..5b60587614 --- /dev/null +++ b/openecomp-ui/fixture/data/softwareProduct.json @@ -0,0 +1,96 @@ +{ + "name": "VSP5", + "version": "0.1", + "id": "4730033D16C64E3CA556AB0AC4478218", + "description": "A software model for Fortigate. Nam hendrerit sollicitudin semper. Aenean consectetur nisi sit amet ante sodales consectetur. Nullam rutrum massa in pellentesque ' +elementum. Aliquam efficitur tellus lacus, eget iaculis justo iaculis eu. ", + "categoryId": "category", + "vendorId": "1", + "checkinStatus": "CHECK_OUT", + "licensingData": "test data", + "validationData": { + "logicalStructure": [ + { + "type": "PPD", + "catalogInstances": [ + { + "name": "PPD1", + "artifacts": [ + { + "name": "chopstick.py" + }, + { + "name": "bread.py" + } + ] + } + ] + } + ], + "importStructure": { + "HEAT": [ + { + "fileName": "sushi.yml", + "env": "soy.env", + "nested": [ + { + "fileName": "salmon.yml", + "env": "skin.env", + "artifacts": [ + { + "name": "rice.py", + "status": "OK" + }, + { + "name": "tuna.py", + "status": "Missing" + } + ] + } + ], + "artifacts": [ + { + "name": "chopstick.py", + "status": "OK" + }, + { + "name": "bread.py", + "status": "Missing" + } + ], + "volume": [ + { + "fileName": "fishtank.yml", + "env": "middletown.env" + } + ], + "network": [ + { + "fileName": "fishnet.yml", + "env": "ship.env" + } + ] + } + ], + "volume": [ + { + "fileName": "vol1.yml", + "env": "e1.env" + }, + { + "fileName": "vol2.yml", + "env": "e2.env" + } + ], + "network": [ + { + "fileName": "net1.yml", + "env": "env1.env" + }, + { + "fileName": "net2.yml", + "env": "env2.env" + } + ] + } + } +} diff --git a/openecomp-ui/fixture/data/softwareProductList.json b/openecomp-ui/fixture/data/softwareProductList.json new file mode 100644 index 0000000000..4554abc95f --- /dev/null +++ b/openecomp-ui/fixture/data/softwareProductList.json @@ -0,0 +1,34 @@ +{ + "results": [ + { + "name": "Software Product 1", + "version": "1.0", + "id": "1", + "category": "Category1", + "subCategory": "Sub Category1", + "vendor": "1", + "status": "string", + "checkinStatus": "string" + }, + { + "name": "Software Product 2", + "version": "1.0", + "id": "2", + "category": "Category2", + "subCategory": "Sub Category2", + "vendor": "2", + "status": "string", + "checkinStatus": "string" + }, + { + "name": "Software Product 3", + "version": "1.0", + "id": "3", + "category": "Category3", + "subCategory": "Sub Category3", + "vendor": "3", + "status": "string", + "checkinStatus": "string" + } + ] +} diff --git a/openecomp-ui/fixture/express.js b/openecomp-ui/fixture/express.js new file mode 100644 index 0000000000..ed8bf956f9 --- /dev/null +++ b/openecomp-ui/fixture/express.js @@ -0,0 +1,231 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var args = process.argv.slice(2); + +function defineRoutes(router) { + + //LICENSE-MODELS + router.get('/v1.0/vendor-license-models', licenseModelsList); + + //FEATURE-GROUP + router.get('/v1.0/vendor-license-models/:licenseModelId/feature-groups', featureGroupList); + router.get('/v1.0/vendor-license-models/:licenseModelId/feature-groups/:featureGroupId', featureGroup); + router.post('/v1.0/vendor-license-models/:licenseModelId/feature-groups', addFeatureGroup); + router.delete('/v1.0/vendor-license-models/:licenseModelId/feature-groups/:featureGroupId', deletefeatureGroup); + router.put('/v1.0/vendor-license-models/:licenseModelId/feature-groups/:featureGroupId', updatefeatureGroup); + + + + //LICENSE-AGREEMENT + router.get('/v1.0/vendor-license-models/:licenseModelId/license-agreements', licenseAgreementList); + router.post('/v1.0/vendor-license-models/:licenseModelId/license-agreements/', addLicenseAgreement); + router.delete('/v1.0/vendor-license-models/:licenseModelId/license-agreements/:licenseAgreementId', deleteLicenseAgreement); + router.put('/v1.0/vendor-license-models/:licenseModelId/license-agreements/:licenseAgreementId', updateLicenseAgreement); + + //ENTITLEMENT POOLS + router.get('/v1.0/vendor-license-models/:licenseModelId/entitlement-pools', entitlementPoolsList); + router.post('/v1.0/vendor-license-models/:licenseModelId/entitlement-pools', addEntitlementPool); + router.put('/v1.0/vendor-license-models/:licenseModelId/entitlement-pools/:entitlementPoolId', updateEntitlementPool); + router.delete('/v1.0/vendor-license-models/:licenseModelId/entitlement-pools/:entitlementPoolId', deleteEntitlementPool); + + //LICENSE KEY GROUPS + router.get('/v1.0/vendor-license-models/:licenseModelId/license-key-groups', licenseKeyGroupsList); + router.post('/v1.0/vendor-license-models/:licenseModelId/license-key-groups', addLicenseKeyGroup); + router.delete('/v1.0/vendor-license-models/:licenseModelId/license-key-groups/:licenseKeyGroupId', deleteLicenseKeyGroup); + router.put('/v1.0/vendor-license-models/:licenseModelId/license-key-groups/:licenseKeyGroupId', updateLicenseKeyGroup); + + //VENDOR SOFTWARE PRODUCT + + router.post('/v1.0/vendor-software-products/:vspId/upload', softwareProductUpload); + router.get('/v1.0/vendor-software-products/:vspId', getSoftwareProduct); + router.get('/v1.0/vendor-software-products', softwareProductList); + + router.put('/v1.0/vendor-software-products/:vspId/processes/:prcId', putSoftwareProductProcess); + router.post('/v1.0/vendor-software-products/:vspId/processes', postSoftwareProductProcess); +} + + +function licenseModelsList(req, res) { + res.json(require('./data/licenseModels')); +} + +function featureGroupList(req, res) { + res.json(require('./data/featureGroups')); +} + +function featureGroup(req, res) { + res.json(require('./data/featureGroup')); +} + +function deletefeatureGroup(req, res) { + res.json({ + returnCode: 'OK' + }); +} + + +function updatefeatureGroup(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +function addFeatureGroup(req,res) { + var id = Math.floor(Math.random() * (100 - 1) + 1).toString(); + res.json({ + returnCode: 'OK', + value: id + }) +} + +/** ENTITLEMENT POOLS **/ +function entitlementPoolsList(req, res) { + res.json(require('./data/entitlementPools')); +} + +function updateEntitlementPool(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +function addEntitlementPool(req,res) { + var id = Math.floor(Math.random() * (100 - 1) + 1).toString(); + res.json({ + returnCode: 'OK', + value: id + }) +} + +function deleteEntitlementPool(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +/** LICENSE KEY GROUPS */ + +function licenseKeyGroupsList(req, res) { + res.json(require('./data/licenseKeyGroups')); +} + +function addLicenseKeyGroup(req,res) { + var id = Math.floor(Math.random() * (100 - 1) + 1).toString(); + res.json({ + returnCode: 'OK', + value: id + }) +} + +function deleteLicenseKeyGroup(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +function updateLicenseKeyGroup(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +function licenseAgreementList(req, res) { + res.json(require('./data/licenseAgreementList')); +} + + +function addLicenseAgreement(req,res) { + var id = Math.floor(Math.random() * (100 - 1) + 1).toString(); + res.json({ + returnCode: 'OK', + value: id + }) +} +function deleteLicenseAgreement(req, res) { + res.json({ + returnCode: 'OK' + }); +} +function updateLicenseAgreement(req, res) { + res.json({ + returnCode: 'OK' + }); +} + +/** VENDOR SOFTWARE PRODUCT */ + +function softwareProductUpload(req, res) { + res.json({ + status: 'SUCCESS' + }); +} + +function getSoftwareProduct(req, res) { + res.json(require('./data/softwareProduct')); +} + + +function putSoftwareProductProcess(req, res) { + res.json({ + status: 'SUCCESS' + }); +} + +function postSoftwareProductProcess(req, res) { + var id = Math.floor(Math.random() * (100 - 1) + 1).toString(); + res.json({ + returnCode: 'OK', + value: id + }); +} + + + + +function createFixtureServer(port) { + var express = require('express'); + var app = express(); + var bodyParser = require('body-parser'); + app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.json()); + + var router = express.Router(); + + defineRoutes(router); + + app.use('/api', router); + app.use('/onboarding-api', router); + app.use('/sdc1/feProxy/onboarding-api', router); + + app.listen(port); + + console.log('Fixture server is up. port->', port); + //console.log(router.stack); + return app; +} + +/** SOFTWARE PRODUCT LIST **/ +function softwareProductList(req, res) { + res.json(require('./data/softwareProductList')); +} + + +createFixtureServer(args[0]); diff --git a/openecomp-ui/fixture/fixture.js b/openecomp-ui/fixture/fixture.js new file mode 100644 index 0000000000..7e5b263c5c --- /dev/null +++ b/openecomp-ui/fixture/fixture.js @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +'use strict'; + +const PORT = 4000; + +function startFixtureServer() { + return require('child_process').fork('fixture/express', [PORT]); +} + +function buildProxyMiddleware() { + return require('./middleware')(PORT); +} + +function getProxyData(fixtureServerOptions, req, res, next) { + if (!fixtureServerOptions.serverProcess) { + fixtureServerOptions.serverProcess = startFixtureServer(); + } + + return fixtureServerOptions.proxy(req, res, next); +} + +function wrapFixture(fixtureServerOptions, req, res, next) { + if (fixtureServerOptions.proxy) { + return getProxyData(fixtureServerOptions, req, res, next); + } else { + next(); + } +} + + +module.exports = function fixture(options) { + + let proxy; + if(options.enabled) { + proxy = buildProxyMiddleware(); + } + + let fixtureServerOptions = { + proxy, + serverProcess: null + }; + + (function startWatch() { + var nodeWatch = require('node-watch'); + + nodeWatch(['fixture/data', 'fixture/express.js'], function () { + if (fixtureServerOptions.proxy && fixtureServerOptions.serverProcess) { + fixtureServerOptions.serverProcess.kill(); + fixtureServerOptions.serverProcess = startFixtureServer(); + } + }); + + nodeWatch(['devConfig.json'], function () { + let devConfigDefaults = require('../devConfig.defaults'); + require('fs').readFile('devConfig.json', (err, data) => { + if (err) throw err; + const config = Object.assign({}, devConfigDefaults, JSON.parse(data)); + if (config.useFixture) { + fixtureServerOptions.proxy = proxy || buildProxyMiddleware(); + } + else { + fixtureServerOptions.proxy = null; + if (fixtureServerOptions.serverProcess) { + fixtureServerOptions.serverProcess.kill(); + fixtureServerOptions.serverProcess = null; + } + } + }); + }); + + })(); + + return (req, res, next) => wrapFixture(fixtureServerOptions, req, res, next); +}; diff --git a/openecomp-ui/fixture/middleware.js b/openecomp-ui/fixture/middleware.js new file mode 100644 index 0000000000..915bb81cab --- /dev/null +++ b/openecomp-ui/fixture/middleware.js @@ -0,0 +1,24 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +module.exports = function createProxyMiddleware(port) { + var proxy = require('http-proxy-middleware'); + return proxy(['/api', '/onboarding-api'], {target: 'http://localhost:' + port}); +}; diff --git a/openecomp-ui/gulpfile.js b/openecomp-ui/gulpfile.js new file mode 100644 index 0000000000..57885ec6ca --- /dev/null +++ b/openecomp-ui/gulpfile.js @@ -0,0 +1,292 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +'use strict'; + +var path = require('path'); +var gulp = require('gulp'); +var gulpHelpers = require('gulp-helpers'); +var taskMaker = gulpHelpers.taskMaker(gulp); +var _ = gulpHelpers.framework('_'); +var runSequence = gulpHelpers.framework('run-sequence'); +var i18nTask = require('./tools/gulp/tasks/i18n'); +var prodTask = require('./tools/gulp/tasks/prod'); +var gulpCssUsage = require('gulp-css-usage').default; +var webpack = require('webpack'); +var WebpackDevServer = require('webpack-dev-server'); + +var localDevConfig = {}; +try { + localDevConfig = require('./devConfig'); +} catch (e) { +} +var devConfig = Object.assign({}, require('./devConfig.defaults'), localDevConfig); +var webpackConfig = require('./webpack.config'); + +function defineTasks(mode) { + let appName = 'onboarding'; + let dist = 'dist/' + mode + '/'; + + let path = { + locales: 'i18n/', + jssource: 'src/**/*.js', + jsxsource: 'src/**/*.jsx', + html: '**/*.html', + output: dist, + assets: './resources/**/*.{css,png,svg,eot,ttf,woff,woff2,otf}', + json: './src/**/*.json', + index: './src/index.html', + heat: './src/heat.html', + watch: ['./src/**'], + scss: './resources/scss/**/*.scss', + css: dist + '/css', + war: [dist + 'index.html', dist + 'punch-outs_en.js', dist + '**/*.{css,png,svg,eot,ttf,woff,woff2,otf}', dist + '**/*(config.json|locale.json)', 'tools/gulp/deployment/**', 'webapp-onboarding/**'], + heatWar: [dist + 'heat.html', dist + 'heat-validation_en.js', dist + '**/*.{css,png,svg,eot,ttf,woff,woff2,otf}', dist + '**/*(config.json|locale.json)', 'webapp-heat-validation/**'], + wardest: 'dist/' + }; + + taskMaker.defineTask('clean', {taskName: 'clean', src: path.output}); + taskMaker.defineTask('copy', {taskName: 'copy-assets', src: path.assets, dest: path.output}); + taskMaker.defineTask('copy', { + taskName: 'copy-json', + src: path.json, + dest: path.output, + changed: {extension: '.json'} + }); + taskMaker.defineTask('copy', { + taskName: 'copy-index.html', + src: path.index, + dest: path.output, + rename: 'index.html' + }); + taskMaker.defineTask('copy', { + taskName: 'copy-heat.html', + src: path.heat, + dest: path.output, + rename: 'heat.html' + }); + taskMaker.defineTask('sass', { + taskName: 'sass', + src: path.scss, + dest: path.css, + config: {outputStyle: 'compressed'} + }); + taskMaker.defineTask('compress', { + taskName: 'compress-war', + src: path.war, + filename: appName + '.war', + dest: path.wardest + }); + taskMaker.defineTask('compress', { + taskName: 'compress-heat-war', + src: path.heatWar, + filename: 'heat-validation.war', + dest: path.wardest + }); + taskMaker.defineTask('watch', { + taskName: 'watch-stuff', + src: [path.assets, path.json, path.index, path.heat], + tasks: ['copy-stuff'] + }); + taskMaker.defineTask('watch', {taskName: 'watch-sass', src: path.scss, tasks: ['sass']}); + + gulp.task('copy-stuff', callback => { + return runSequence(['copy-assets', 'copy-json', 'copy-index.html', 'copy-heat.html'], callback); + }); + + gulp.task('i18n', () => { + return i18nTask({ + outputPath: path.output, + localesPath: path.locales, + lang: 'en' + }).catch(err => { + console.log('i18n Task : Error! ', err); + throw err; + }); + }); + + + gulp.task('dependencies', () => { + //TODO: + }); + +} + +gulp.task('dev', callback => { + defineTasks('dev'); + return runSequence('clean', ['i18n', 'copy-stuff'], 'webpack-dev-server', ['watch-stuff'], callback); +}); + +// Production build +gulp.task('build', callback => { + defineTasks('prod'); + return runSequence('clean', ['copy-stuff', 'i18n'], 'prod', ['compress-war', 'compress-heat-war'], callback); +}); + +gulp.task('default', ['dev']); + +gulp.task('prod', () => { + + // configure webpack for production + let webpackProductionConfig = Object.create(webpackConfig); + + for (let name in webpackProductionConfig.entry) { + webpackProductionConfig.entry[name] = webpackProductionConfig.entry[name].filter(path => !path.startsWith('webpack')); + } + + webpackProductionConfig.cache = true; + webpackProductionConfig.output = { + path: path.join(__dirname, 'dist/prod'), + publicPath: '/onboarding/', + filename: '[name].js' + }; + webpackProductionConfig.resolveLoader = { + root: [path.resolve('.')], + alias: { + 'config-json-loader': 'tools/webpack/config-json-loader/index.js' + } + }; + + // remove source maps + webpackProductionConfig.devtool = undefined; + webpackProductionConfig.module.preLoaders = webpackProductionConfig.module.preLoaders.filter(preLoader => preLoader.loader != 'source-map-loader'); + webpackProductionConfig.module.loaders.forEach(loader => { + if (loader.loaders && loader.loaders[0] === 'style') { + loader.loaders = loader.loaders.map(loaderName => loaderName.replace('?sourceMap', '')); + } + }); + + webpackProductionConfig.module.loaders.push({test: /config.json$/, loaders: ['config-json-loader']}); + webpackProductionConfig.eslint = { + configFile: './.eslintrc', + failOnError: true + }; + webpackProductionConfig.babel = {//TODO: remove this when UglifyJS will support user or + // Webpack 2.0 + presets: ['es2015', 'stage-0', 'react'] + } + webpackProductionConfig.plugins = [ + new webpack.DefinePlugin({ + 'process.env': { + // This has effect on the react lib size + 'NODE_ENV': JSON.stringify('production') + }, + DEBUG: false, + DEV: false + }), + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin() + ]; + + // run production build + return prodTask({ + webpackProductionConfig, + outDir: 'dist/prod' + }) + .then(() => { + }) + .catch(err => { + if (err && err.stack) { + console.error(err, err.stack); + } + throw new Error('Webpack build FAILED'); + }); +}); + +gulp.task('webpack-dev-server', () => { + // modify some webpack config options for development + let myConfig = Object.create(webpackConfig); + + myConfig.devServer.setup = server => { + let fixture = require('./fixture/fixture'); + let proxy = require('http-proxy-middleware'); + let proxyConfigDefaults = { + changeOrigin: true, + secure: false, + onProxyRes: (proxyRes, req, res) => { + let setCookie = proxyRes.headers['set-cookie']; + if (setCookie) { + setCookie[0] = setCookie[0].replace(/\bSecure\b(; )?/, ''); + } + } + }; + + let middlewares = [ + (req, res, next) => { + let match = req.url.match(/^(.*)_en.js$/); + let newUrl = match && match[1] + '.js'; + if (newUrl) { + console.log(`REWRITING URL: ${req.url} -> ${newUrl}`); + req.url = newUrl; + } + next(); + }, + fixture({ + enabled: devConfig.useFixture + }) + ]; + + // standalon back-end (proxyTarget) has higher priority, so it should be first + if (devConfig.proxyTarget) { + middlewares.push( + proxy(['/api', '/onboarding-api', '/sdc1/feProxy/onboarding-api'], Object.assign({}, proxyConfigDefaults, { + target: devConfig.proxyTarget, + pathRewrite: { + '/sdc1/feProxy/onboarding-api': '/onboarding-api' + } + })) + ) + } + + // Ecorp environment (proxyATTTarget) has lower priority, so it should be second + if (devConfig.proxyATTTarget) { + middlewares.push( + proxy(['/sdc1', '/onboarding-api'], Object.assign({}, proxyConfigDefaults, { + target: devConfig.proxyATTTarget, + pathRewrite: { + // Workaround for some weird proxy issue + '/sdc1/feProxy/onboarding-api': '/sdc1/feProxy/onboarding-api', + '/onboarding-api': '/sdc1/feProxy/onboarding-api' + } + })) + ) + } + server.use(middlewares); + }; + + // Start a webpack-dev-server + let server = new WebpackDevServer(webpack(myConfig), myConfig.devServer); + server.listen(myConfig.devServer.port, '0.0.0.0', err => { + if (err) { + throw new Error('webpack-dev-server' + err); + } + }); +}); + + +gulp.task('gulp-css-usage', callback => { + return gulp.src('src/**/*.jsx').pipe(gulpCssUsage({css: 'dist/dev/css/style.css', babylon: ['objectRestSpread']})); +}); + +gulp.task('css-usage', callback => { + defineTasks('dev'); + runSequence('sass', 'gulp-css-usage'); +}); + diff --git a/openecomp-ui/karma.conf.js b/openecomp-ui/karma.conf.js new file mode 100644 index 0000000000..91c5040942 --- /dev/null +++ b/openecomp-ui/karma.conf.js @@ -0,0 +1,102 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var path = require('path'); +var isparta = require('isparta'); + +module.exports = function (config) { + config.set({ + + browsers: [process.env.JENKINS_HOME ? 'Firefox' : 'Chrome'], + + singleRun: true, + + autoWatchBatchDelay: 50, + + frameworks: ['mocha'], + + files: [ + 'tests.webpack.js' + ], + + preprocessors: { + 'tests.webpack.js': ['webpack', 'sourcemap'], + 'src/**/*.jsx': ['coverage'] + }, + + reporters: ['progress', 'coverage'], + + coverageReporter: { + dir: 'dist/coverage/', + reporters: [ + {type: 'html'}, + {type: 'text-summary'} + ], + includeAllSources: true, + instrumenters: {isparta: isparta}, + instrumenter: { + '**/*.js': 'isparta', + '**/*.jsx': 'isparta' + }, + instrumenterOptions: { + isparta: { + embedSource: true, + noAutoWrap: true, + } + } + }, + + webpack: { + devtool: 'inline-source-map', + resolve: { + root: [path.resolve('.')], + alias: { + i18nJson: 'nfvo-utils/i18n/locale.json', + 'nfvo-utils/RestAPIUtil.js': 'test-utils/MockRest.js', + 'nfvo-utils': 'src/nfvo-utils', + 'nfvo-components': 'src/nfvo-components', + 'sdc-app': 'src/sdc-app' + } + }, + module: { + preLoaders: [ + {test: /\.js$/, exclude: /(src|node_modules)/, loader: 'eslint-loader'}, + {test: /\.(js|jsx)$/, exclude: /(test|test\.js|node_modules)/, loader: 'isparta'} + ], + loaders: [ + {test: /\.(js|jsx)$/, exclude: /node_modules/, loader: 'babel-loader'}, + {test: /\.json$/, loaders: ['json']}, + {test: /\.(css|scss|png|jpg|svg|ttf|eot|otf|woff|woff2)(\?.*)?$/, loader: 'ignore-loader'}, + ] + }, + eslint: { + configFile: './.eslintrc', + emitError: true, + emitWarning: true, + failOnError: true + }, + }, + + webpackServer: { + noInfo: true + } + + }); +}; diff --git a/openecomp-ui/package.json b/openecomp-ui/package.json new file mode 100644 index 0000000000..1d9a479e43 --- /dev/null +++ b/openecomp-ui/package.json @@ -0,0 +1,102 @@ +{ + "name": "dox-ui", + "version": "1.0.0", + "description": "", + "author": "ECOMP", + "license": "SEE LICENSE IN LICENSE", + "scripts": { + "start": "gulp dev", + "build": "gulp build", + "test": "karma start", + "test-dev": "karma start --auto-watch --no-single-run" + }, + "dependencies": { + "classnames": "^2.2.5", + "core-js": "^2.4.0", + "dox-sequence-diagram-ui": "file:../dox-sequence-diagram-ui", + "history": "^1.13.1", + "immutable": "^3.7.5", + "intl": "^1.0.1", + "intl-format-cache": "^2.0.5", + "intl-messageformat": "^1.2.0", + "intl-relativeformat": "^1.2.0", + "jquery": "^2.2.2", + "lodash": "^4.13.1", + "md5": "^2.1.0", + "react": "~15.3.0", + "react-bootstrap": "^0.28.1", + "react-dom": "~15.3.0", + "react-dropzone": "3.4.0", + "react-fontawesome": "^0.3.3", + "react-redux": "^4.4.1", + "react-select": "^1.0.0-beta13", + "redux": "^3.3.1", + "uuid-js": "^0.7.5", + "validator": "^4.3.0" + }, + "devDependencies": { + "babel-core": "^6.9.1", + "babel-eslint": "^6.0.2", + "babel-loader": "^6.2.4", + "babel-plugin-transform-class-properties": "^6.10.2", + "babel-plugin-transform-es2015-destructuring": "^6.9.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", + "babel-plugin-transform-es2015-spread": "^6.8.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0", + "chai": "^3.5.0", + "css-loader": "^0.23.1", + "deep-freeze": "0.0.1", + "eslint-loader": "^1.3.0", + "eslint-plugin-import": "^0.8.1", + "eslint-plugin-react": "^3.14.0", + "expect": "^1.20.1", + "express": "^4.13.3", + "file-loader": "^0.8.5", + "gulp": "^3.9.1", + "gulp-clean": "^0.3.1", + "gulp-css-usage": "^2.0.0", + "gulp-helpers": "^5.0.0", + "gulp-jsx-coverage": "^0.3.8", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.4", + "html-loader": "^0.4.3", + "http-proxy-middleware": "^0.8.2", + "ignore-loader": "^0.1.1", + "isparta": "^4.0.0", + "isparta-loader": "^2.0.0", + "istanbul": "^1.0.0-alpha.2", + "istanbul-instrumenter-loader": "^0.2.0", + "jsdom": "^8.3.0", + "json-loader": "^0.5.4", + "jsx-loader": "^0.13.2", + "karma": "^0.13.22", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^1.0.1", + "karma-cli": "^1.0.0", + "karma-coverage": "^1.0.0", + "karma-firefox-launcher": "^1.0.0", + "karma-mocha": "^1.0.1", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^1.7.0", + "mkdirp": "^0.5.1", + "mocha": "^2.4.5", + "node-watch": "^0.3.5", + "prompt": "^0.2.14", + "react-addons-test-utils": "~15.3.0", + "react-hot-loader": "^1.3.0", + "sass-loader": "^3.1.2", + "sinon": "^1.17.3", + "source-map-loader": "^0.1.5", + "style-loader": "^0.13.0", + "url-loader": "^0.5.7", + "webpack": "^1.13.1", + "webpack-dev-server": "^1.14.1" + }, + "engines": { + "node": ">=5.1", + "npm": ">=3.3" + } +} diff --git a/openecomp-ui/pom.xml b/openecomp-ui/pom.xml new file mode 100644 index 0000000000..456469e044 --- /dev/null +++ b/openecomp-ui/pom.xml @@ -0,0 +1,84 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.openecomp.sdc</groupId> + <artifactId>sdc-main</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + + <groupId>org.openecomp.sdc.onboarding</groupId> + <artifactId>onboarding-fe</artifactId> + <name>onboarding-ui-war</name> + + <packaging>war</packaging> + + <build> + <plugins> + <plugin> + <artifactId>maven-war-plugin</artifactId> + <configuration> + <webXml>webapp-onboarding\WEB-INF\web.xml</webXml> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>rackspace</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <repositories> + <repository> + <id>rackspace-public</id> + <name>Rackspace</name> + <url>https://10.208.197.75:8443/repository/maven-public/</url> + <layout>default</layout> + </repository> + + <repository> + <id>virtuos</id> + <name>Virtuos</name> + <url>http://nexus.virtuos.uos.de/nexus/content/repositories/public/</url> + <layout>default</layout> + </repository> + + <repository> + <id>apache-public</id> + <name>Apache-Public</name> + <url>https://repository.apache.org/content/groups/public/</url> + <layout>default</layout> + </repository> + + <repository> + <id>elasticsearch-releases</id> + <url>https://maven.elasticsearch.org/releases</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + </repositories> + + <distributionManagement> + <snapshotRepository> + <id>rackspace-snapshots</id> + <name>Rackspace-Snapshots</name> + <url>https://10.208.197.75:8443/repository/maven-snapshots/</url> + </snapshotRepository> + + <repository> + <id>rackspace-public</id> + <name>Rackspace</name> + <url>https://10.208.197.75:8443/repository/maven-releases/</url> + </repository> + </distributionManagement> + </profile> + </profiles> + +</project> diff --git a/openecomp-ui/readMe.txt b/openecomp-ui/readMe.txt new file mode 100644 index 0000000000..6b70dfc5bb --- /dev/null +++ b/openecomp-ui/readMe.txt @@ -0,0 +1,25 @@ +# OpenECOMP SDC(UI) + +--- +--- + +# Steps + +### Install nodejs & gulp + +#### Download nodejs from here : https://nodejs.org/en/ (take the "current" version with latest features) & install it. +#### Install gulp by running the following command : npm install --global gulp-cli + +### Install DOX-UI a + +#### Pull for latest changes. +#### Go to folder dox-sequence-diagram-ui +#### Give the following command : run npm install +#### Wait for it.. +#### Go to folder dox-ui +#### run npm install +#### Create a copy of devConfig.defaults.json file and name it devConfig.json (we already configured git to ignore it so it will not be pushed). +#### In that file, populate the fields of the IP addresses of your BE machine you'd like to connect (pay attention, it is a JSON file): For example, http://<host>:<port> +#### After everything was successful, run gulp. +#### After server was up, your favourite UI will wait for you at : http://localhost:9000/sdc1/proxy-designer1#/onboardVendor + diff --git a/openecomp-ui/resources/css/font-awesome.min.css b/openecomp-ui/resources/css/font-awesome.min.css new file mode 100644 index 0000000000..24fcc04c4e --- /dev/null +++ b/openecomp-ui/resources/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}
\ No newline at end of file diff --git a/openecomp-ui/resources/fonts/fontawesome-webfont.eot b/openecomp-ui/resources/fonts/fontawesome-webfont.eot Binary files differnew file mode 100644 index 0000000000..33b2bb8005 --- /dev/null +++ b/openecomp-ui/resources/fonts/fontawesome-webfont.eot diff --git a/openecomp-ui/resources/fonts/fontawesome-webfont.svg b/openecomp-ui/resources/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000..1ee89d4368 --- /dev/null +++ b/openecomp-ui/resources/fonts/fontawesome-webfont.svg @@ -0,0 +1,565 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> +<metadata></metadata> +<defs> +<font id="fontawesomeregular" horiz-adv-x="1536" > +<font-face units-per-em="1792" ascent="1536" descent="-256" /> +<missing-glyph horiz-adv-x="448" /> +<glyph unicode=" " horiz-adv-x="448" /> +<glyph unicode="	" horiz-adv-x="448" /> +<glyph unicode=" " horiz-adv-x="448" /> +<glyph unicode="¨" horiz-adv-x="1792" /> +<glyph unicode="©" horiz-adv-x="1792" /> +<glyph unicode="®" horiz-adv-x="1792" /> +<glyph unicode="´" horiz-adv-x="1792" /> +<glyph unicode="Æ" horiz-adv-x="1792" /> +<glyph unicode="Ø" horiz-adv-x="1792" /> +<glyph unicode=" " horiz-adv-x="768" /> +<glyph unicode=" " horiz-adv-x="1537" /> +<glyph unicode=" " horiz-adv-x="768" /> +<glyph unicode=" " horiz-adv-x="1537" /> +<glyph unicode=" " horiz-adv-x="512" /> +<glyph unicode=" " horiz-adv-x="384" /> +<glyph unicode=" " horiz-adv-x="256" /> +<glyph unicode=" " horiz-adv-x="256" /> +<glyph unicode=" " horiz-adv-x="192" /> +<glyph unicode=" " horiz-adv-x="307" /> +<glyph unicode=" " horiz-adv-x="85" /> +<glyph unicode=" " horiz-adv-x="307" /> +<glyph unicode=" " horiz-adv-x="384" /> +<glyph unicode="™" horiz-adv-x="1792" /> +<glyph unicode="∞" horiz-adv-x="1792" /> +<glyph unicode="≠" horiz-adv-x="1792" /> +<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" /> +<glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " /> +<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z " /> +<glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" /> +<glyph unicode="" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" /> +<glyph unicode="" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" /> +<glyph unicode="" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" /> +<glyph unicode="" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" /> +<glyph unicode="" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" /> +<glyph unicode="" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" /> +<glyph unicode="" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" /> +<glyph unicode="" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> +<glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452zM0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57 q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5 q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14z" /> +<glyph unicode="" horiz-adv-x="1408" d="M555 15q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5t4.5 -83.5t12 -66.5zM541 761q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142 q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13q0 -50 4 -151t4 -152q0 -27 -0.5 -80t-0.5 -79q0 -46 1 -69zM0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5 t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5 t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12z" /> +<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1744 128q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80zM81 1407l54 -27q12 -5 211 -5q44 0 132 2 t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5 q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44v383z" /> +<glyph unicode="" d="M81 1407l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1t-103 1 t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44v383zM1310 125q12 0 42 -19.5t57.5 -41.5 t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49 t26 49q4 3 36 30t59.5 49t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" /> +<glyph unicode="" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> +<glyph unicode="" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" /> +<glyph unicode="" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" /> +<glyph unicode="" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" /> +<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" /> +<glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" /> +<glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" /> +<glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1171 1235l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45t19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1107 659l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45t19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45z" /> +<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" /> +<glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" /> +<glyph unicode="" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" /> +<glyph unicode="" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" /> +<glyph unicode="" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" /> +<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" /> +<glyph unicode="" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" /> +<glyph unicode="" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " /> +<glyph unicode="" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" /> +<glyph unicode="" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" /> +<glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " /> +<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1536 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1664 1088v-512q0 -24 -16.5 -42.5t-40.5 -21.5l-1044 -122q13 -60 13 -70q0 -16 -24 -64h920q26 0 45 -19t19 -45 t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 11 8 31.5t16 36t21.5 40t15.5 29.5l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t19.5 -15.5t13 -24.5t8 -26t5.5 -29.5t4.5 -26h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" /> +<glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" /> +<glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-188v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-532q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960z" /> +<glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" /> +<glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" /> +<glyph unicode="" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" /> +<glyph unicode="" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" /> +<glyph unicode="" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" /> +<glyph unicode="" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" /> +<glyph unicode="" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" /> +<glyph unicode="" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" /> +<glyph unicode="" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> +<glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" /> +<glyph unicode="" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" /> +<glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" /> +<glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" /> +<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM246 128h1300q-266 300 -266 832q0 51 -24 105t-69 103t-121.5 80.5t-169.5 31.5t-169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -532 -266 -832z M1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5 t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> +<glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" /> +<glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" /> +<glyph unicode="" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" /> +<glyph unicode="" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" /> +<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" /> +<glyph unicode="" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" /> +<glyph unicode="" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " /> +<glyph unicode="" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " /> +<glyph unicode="" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" /> +<glyph unicode="" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" /> +<glyph unicode="" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" /> +<glyph unicode="" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" /> +<glyph unicode="" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" /> +<glyph unicode="" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" /> +<glyph unicode="" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" /> +<glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" /> +<glyph unicode="" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" /> +<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" /> +<glyph unicode="" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" /> +<glyph unicode="" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" /> +<glyph unicode="" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" /> +<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5 t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M384 736q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64zM1120 512q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704zM1120 256q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704 q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" /> +<glyph unicode="" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" /> +<glyph unicode="" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" /> +<glyph unicode="" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" /> +<glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" /> +<glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" /> +<glyph unicode="" horiz-adv-x="1792" d="M526 142q0 -53 -37.5 -90.5t-90.5 -37.5q-52 0 -90 38t-38 90q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1522 142q0 -52 -38 -90t-90 -38q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM558 1138q0 -66 -47 -113t-113 -47t-113 47t-47 113t47 113t113 47t113 -47t47 -113z M1728 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1088 1344q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1618 1138q0 -93 -66 -158.5t-158 -65.5q-93 0 -158.5 65.5t-65.5 158.5 q0 92 65.5 158t158.5 66q92 0 158 -66t66 -158z" /> +<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" /> +<glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" /> +<glyph unicode="" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" /> +<glyph unicode="" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" /> +<glyph unicode="" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" /> +<glyph unicode="" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" /> +<glyph unicode="" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" /> +<glyph unicode="" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" /> +<glyph unicode="" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" /> +<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" /> +<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" /> +<glyph unicode="" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" /> +<glyph unicode="" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" /> +<glyph unicode="" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" /> +<glyph unicode="" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" /> +<glyph unicode="" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" /> +<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" /> +<glyph unicode="" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" /> +<glyph unicode="" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" /> +<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" /> +<glyph unicode="" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" /> +<glyph unicode="" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" /> +<glyph unicode="" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" /> +<glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" /> +<glyph unicode="" d="M1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472zM896 992q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544z" /> +<glyph unicode="" d="M1468 1060q14 -14 28 -36h-472v472q22 -14 36 -28zM992 896h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28zM1152 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" /> +<glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" /> +<glyph unicode="" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" /> +<glyph unicode="" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" /> +<glyph unicode="" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" /> +<glyph unicode="" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" /> +<glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" /> +<glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" /> +<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" /> +<glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" /> +<glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" /> +<glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M944 207l80 -237q-23 -35 -111 -66t-177 -32q-104 -2 -190.5 26t-142.5 74t-95 106t-55.5 120t-16.5 118v544h-168v215q72 26 129 69.5t91 90t58 102t34 99t15 88.5q1 5 4.5 8.5t7.5 3.5h244v-424h333v-252h-334v-518q0 -30 6.5 -56t22.5 -52.5t49.5 -41.5t81.5 -14 q78 2 134 29z" /> +<glyph unicode="" d="M1136 75l-62 183q-44 -22 -103 -22q-36 -1 -62 10.5t-38.5 31.5t-17.5 40.5t-5 43.5v398h257v194h-256v326h-188q-8 0 -9 -10q-5 -44 -17.5 -87t-39 -95t-77 -95t-118.5 -68v-165h130v-418q0 -57 21.5 -115t65 -111t121 -85.5t176.5 -30.5q69 1 136.5 25t85.5 50z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" /> +<glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" /> +<glyph unicode="" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" /> +<glyph unicode="" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" /> +<glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" /> +<glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1000 1102l37 194q5 23 -9 40t-35 17h-712q-23 0 -38.5 -17t-15.5 -37v-1101q0 -7 6 -1l291 352q23 26 38 33.5t48 7.5h239q22 0 37 14.5t18 29.5q24 130 37 191q4 21 -11.5 40t-36.5 19h-294q-29 0 -48 19t-19 48v42q0 29 19 47.5t48 18.5h346q18 0 35 13.5t20 29.5z M1227 1324q-15 -73 -53.5 -266.5t-69.5 -350t-35 -173.5q-6 -22 -9 -32.5t-14 -32.5t-24.5 -33t-38.5 -21t-58 -10h-271q-13 0 -22 -10q-8 -9 -426 -494q-22 -25 -58.5 -28.5t-48.5 5.5q-55 22 -55 98v1410q0 55 38 102.5t120 47.5h888q95 0 127 -53t10 -159zM1227 1324 l-158 -790q4 17 35 173.5t69.5 350t53.5 266.5z" /> +<glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" /> +<glyph unicode="" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" /> +<glyph unicode="" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " /> +<glyph unicode="" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" /> +<glyph unicode="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1519 760q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72l-55 163l-153 -53q-29 -9 -50 -9 q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102 t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8zM725 498l310 105l-105 315l-310 -107z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM1280 352v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99q-98 -69 -164 -69v0v0q-66 0 -164 69 q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436q0 -40 28 -68t68 -28h832q40 0 68 28t28 68zM1280 925q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13 t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM1415 679q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15t-1.5 -18.5t9 -16.5 t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21 t14.5 -24t14 -23q63 -107 63 -212zM909 573l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1570 1009q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5l235 678q59 169 59 276q0 42 -6 79zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286 t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 -215q173 0 331.5 68t273 182.5t182.5 273t68 331.5t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5t68 -331.5t182.5 -273 t273 -182.5t331.5 -68z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1086 1536v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360zM1755 954l37 -390l-525 114l147 83q-119 70 -280 99v172q277 -33 481 -157z" /> +<glyph unicode="" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" /> +<glyph unicode="" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" /> +<glyph unicode="" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1509 107q0 -14 -12 -29q-52 -59 -147.5 -83t-196.5 -24q-252 0 -346 107q-12 15 -12 29q0 17 12 29.5t29 12.5q15 0 30 -12q58 -49 125.5 -66t159.5 -17t160 17t127 66q15 12 30 12q17 0 29 -12.5t12 -29.5zM978 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5 t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM1622 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM415 793q-39 27 -88 27q-66 0 -113 -47t-47 -113q0 -72 54 -121q53 141 194 254zM2020 382q0 222 -249 387 q-128 85 -291.5 126.5t-331.5 41.5t-331.5 -41.5t-292.5 -126.5q-249 -165 -249 -387t249 -387q129 -85 292.5 -126.5t331.5 -41.5t331.5 41.5t291.5 126.5q249 165 249 387zM2137 660q0 66 -47 113t-113 47q-50 0 -93 -30q140 -114 192 -256q61 48 61 126zM1993 1335 q0 49 -34.5 83.5t-82.5 34.5q-49 0 -83.5 -34.5t-34.5 -83.5q0 -48 34.5 -82.5t83.5 -34.5q48 0 82.5 34.5t34.5 82.5zM2220 660q0 -65 -33 -122t-89 -90q5 -35 5 -66q0 -139 -79 -255.5t-208 -201.5q-140 -92 -313.5 -136.5t-354.5 -44.5t-355 44.5t-314 136.5 q-129 85 -208 201.5t-79 255.5q0 36 6 71q-53 33 -83.5 88.5t-30.5 118.5q0 100 71 171.5t172 71.5q91 0 159 -60q265 170 638 177l144 456q10 29 40 29q24 0 384 -90q24 55 74 88t110 33q82 0 141 -59t59 -142t-59 -141.5t-141 -58.5q-83 0 -141.5 58.5t-59.5 140.5 l-339 80l-125 -395q349 -15 603 -179q71 63 163 63q101 0 172 -71.5t71 -171.5z" /> +<glyph unicode="" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" /> +<glyph unicode="" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" /> +<glyph unicode="" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M328 1254h204v-983h-532v697h328v286zM328 435v369h-123v-369h123zM614 968v-697h205v697h-205zM614 1254v-204h205v204h-205zM901 968h533v-942h-533v163h328v82h-328v697zM1229 435v369h-123v-369h123zM1516 968h532v-942h-532v163h327v82h-327v697zM1843 435v369h-123 v-369h123z" /> +<glyph unicode="" d="M1046 516q0 -64 -38 -109t-91 -45q-43 0 -70 15v277q28 17 70 17q53 0 91 -45.5t38 -109.5zM703 944q0 -64 -38 -109.5t-91 -45.5q-43 0 -70 15v277q28 17 70 17q53 0 91 -45t38 -109zM1265 513q0 134 -88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101 v-636l211 41v206q51 -19 117 -19q125 0 213 95t88 229zM922 940q0 134 -88.5 229t-213.5 95q-74 0 -141 -36h-186v-840l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2038" d="M1222 607q75 3 143.5 -20.5t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14 q6 -5 28 -23.5t25.5 -22t19 -18t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24 q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33 q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5 t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5zM1282 842q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43 q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5 t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM822 568l48 12l109 -177l-73 -48zM1323 51q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13 t-54 -9.5t-53.5 -7.5t-32 -4.5l-7 43q21 2 60.5 8.5t72 10t60.5 3.5h14zM866 679l-96 -20l-6 17q10 1 32.5 7t34.5 6q19 0 35 -10zM1061 45h31l10 -83l-41 -12v95zM1950 1535v1v-1zM1950 1535l-1 -5l-2 -2l1 3zM1950 1535l1 1z" /> +<glyph unicode="" d="M1167 -50q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16t7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29zM1128 65q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10 q-29 -12 -78 -56q-26 -24 -12 -44q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34zM1483 346q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14 q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106zM1536 506q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44 q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5t19.5 -177.5z" /> +<glyph unicode="" d="M1070 463l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5t-60 145.5q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160z M729 1145l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5q-70 15 -115 71t-45 129q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5 t149.5 -87.5zM1536 78q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30l-152 152l-160 160l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5 q76 -11 126.5 -68.5t50.5 -134.5zM1534 1202q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152l-160 -160l-152 152l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126 t135.5 51q85 0 145 -60.5t60 -145.5z" /> +<glyph unicode="" d="M654 458q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110q-4 -2 -19.5 -4t-18.5 0q6 4 82 92q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5 q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28 q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5zM449 944q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17zM1147 815l63 -227l-139 42zM39 15l694 232v1032l-694 -233v-1031z M1280 332l102 -31l-181 657l-100 31l-216 -536l102 -31l45 110l211 -65zM777 1294l573 -184v380zM1088 -29l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11 q73 -37 159.5 -61.5t157.5 -24.5q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5zM1536 1050v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5 q20 0 20 -21v-418z" /> +<glyph unicode="" horiz-adv-x="1792" d="M288 1152q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h128zM1664 989q58 -34 93 -93t35 -128v-768q0 -106 -75 -181t-181 -75h-864q-66 0 -113 47t-47 113v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48 l152 -152q28 -28 48 -76t20 -88v-163zM928 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 512v128q0 14 -9 23 t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23zM1184 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 256v128q0 14 -9 23t-23 9h-128 q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1536 896v256h-160q-40 0 -68 28t-28 68v160h-640v-512h896z" /> +<glyph unicode="" d="M1344 1536q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280zM512 1248v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 992v-64q0 -14 9 -23t23 -9h64q14 0 23 9 t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 736v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 480v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 160v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM384 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 -96v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM896 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 928v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 160v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1188 988l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68t28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68zM864 1152q0 -93 -65.5 -158.5 t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="1664" d="M780 1064q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5zM438 581q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152q0 80 42 139.5t119 59.5 q76 0 141.5 -55.5t100.5 -134t35 -152.5zM832 608q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146q0 86 56 191.5t139.5 192.5t187.5 146t193 59zM1071 819 q-61 0 -105 39t-63 92.5t-19 113.5q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5zM1503 923q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5q-77 0 -119 59t-42 139q0 74 35 152.5 t100.5 134t141.5 55.5z" /> +<glyph unicode="" horiz-adv-x="768" d="M704 1008q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 -93l640 349v636l-640 -233v-752zM832 772l698 254l-698 254l-698 -254zM1664 1024v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73z " /> +<glyph unicode="" horiz-adv-x="2304" d="M640 -96l384 192v314l-384 -164v-342zM576 358l404 173l-404 173l-404 -173zM1664 -96l384 192v314l-384 -164v-342zM1600 358l404 173l-404 173l-404 -173zM1152 651l384 165v266l-384 -164v-267zM1088 1030l441 189l-441 189l-441 -189zM2176 512v-416q0 -36 -19 -67 t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70 v-400l434 -186q36 -16 57 -48t21 -70z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1848 1197h-511v-124h511v124zM1596 771q-90 0 -146 -52.5t-62 -142.5h408q-18 195 -200 195zM1612 186q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658 q0 -111 57.5 -171.5t166.5 -60.5zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5t45.5 113.5q0 144 -190 144h-260v-294zM0 1282h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204 q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611v1260z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM499 1041h-371v-787h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5 t-56.5 60.5t-79 29.5t-97 8.5zM477 723h-176v184h163q119 0 119 -90q0 -94 -106 -94zM486 388h-185v217h189q124 0 124 -113q0 -104 -128 -104zM1136 356q-68 0 -104 38t-36 107h411q1 10 1 30q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216q0 -135 79 -217 t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20zM1126 722q113 0 124 -122h-254q4 56 39 89t91 33zM964 988h319v-77h-319v77z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1582 954q0 -101 -71.5 -172.5t-172.5 -71.5t-172.5 71.5t-71.5 172.5t71.5 172.5t172.5 71.5t172.5 -71.5t71.5 -172.5zM812 212q0 104 -73 177t-177 73q-27 0 -54 -6l104 -42q77 -31 109.5 -106.5t1.5 -151.5q-31 -77 -107 -109t-152 -1q-21 8 -62 24.5t-61 24.5 q32 -60 91 -96.5t130 -36.5q104 0 177 73t73 177zM1642 953q0 126 -89.5 215.5t-215.5 89.5q-127 0 -216.5 -89.5t-89.5 -215.5q0 -127 89.5 -216t216.5 -89q126 0 215.5 89t89.5 216zM1792 953q0 -189 -133.5 -322t-321.5 -133l-437 -319q-12 -129 -109 -218t-229 -89 q-121 0 -214 76t-118 192l-230 92v429l389 -157q79 48 173 48q13 0 35 -2l284 407q2 187 135.5 319t320.5 132q188 0 321.5 -133.5t133.5 -321.5z" /> +<glyph unicode="" d="M1242 889q0 80 -57 136.5t-137 56.5t-136.5 -57t-56.5 -136q0 -80 56.5 -136.5t136.5 -56.5t137 56.5t57 136.5zM632 301q0 -83 -58 -140.5t-140 -57.5q-56 0 -103 29t-72 77q52 -20 98 -40q60 -24 120 1.5t85 86.5q24 60 -1.5 120t-86.5 84l-82 33q22 5 42 5 q82 0 140 -57.5t58 -140.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v153l172 -69q20 -92 93.5 -152t168.5 -60q104 0 181 70t87 173l345 252q150 0 255.5 105.5t105.5 254.5q0 150 -105.5 255.5t-255.5 105.5 q-148 0 -253 -104.5t-107 -252.5l-225 -322q-9 1 -28 1q-75 0 -137 -37l-297 119v468q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5zM1289 887q0 -100 -71 -170.5t-171 -70.5t-170.5 70.5t-70.5 170.5t70.5 171t170.5 71q101 0 171.5 -70.5t70.5 -171.5z " /> +<glyph unicode="" horiz-adv-x="1792" d="M836 367l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5q-11 27 -14.5 55t4 65t12 55t21.5 64t19 53q78 -12 509 -28zM449 953l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188 l-140 86zM1680 436l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164l-230 367l211 362l7 -173q170 -16 283 -5t170 33zM895 1360q-47 -63 -265 -435l-317 187l-19 12l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5 t36 -39.5t32 -35zM1550 1053l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436l313 195zM1407 1279l142 83l-220 -373l-419 20l151 86q-34 89 -75 166t-75.5 123.5t-64.5 80t-47 46.5l-17 13l405 -1 q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190z" /> +<glyph unicode="" horiz-adv-x="2048" d="M480 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM516 768h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5zM1888 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM2048 544v-384 q0 -14 -9 -23t-23 -9h-96v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-1024v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h768q98 0 179 -63.5t104 -157.5 l105 -419h28q93 0 158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" /> +<glyph unicode="" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" /> +<glyph unicode="" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 1233l-303 -582l24 -31h279v-415h-507l-44 -30l-142 -273l-30 -30h-301v303l303 583l-24 30h-279v415h507l44 30l142 273l30 30h301v-303z" /> +<glyph unicode="" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" /> +<glyph unicode="" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M233 768v-107h70l164 -661h159l128 485q7 20 10 46q2 16 2 24h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21 q-2 26 -7 46l-99 438h90v107h-300z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M429 106v-106h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107 h-290v-107h68l189 -272l-194 -283h-68z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M416 106v-106h327v106h-93v167h137q76 0 118 15q67 23 106.5 87t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92zM769 386h-119v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M1280 320v-320h-1024v192l192 192l128 -128l384 384zM448 512q-80 0 -136 56t-56 136t56 136t136 56t136 -56t56 -136t-56 -136t-136 -56z" /> +<glyph unicode="" d="M640 1152v128h-128v-128h128zM768 1024v128h-128v-128h128zM640 896v128h-128v-128h128zM768 768v128h-128v-128h128zM1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400 v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536h1280zM781 593l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5q0 25 8 52q21 63 120 396v128h128v-128h79 q22 0 39 -13t23 -34zM640 128q53 0 90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45t37.5 -45t90.5 -19z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M620 686q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h131l166 167q16 15 35 7zM1037 -3q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5 q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5t23.5 -42.5q18 -15 40 -15zM826 145q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5t20 -44.5q20 -17 44 -17z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M768 768q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h384zM1260 766q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9l-265 266v90l265 266q9 9 23 9q4 0 12 -2z" /> +<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M480 768q8 11 21 12.5t24 -6.5l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5l-226 301q-14 19 0 38zM1282 467q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21t6.5 24l182 243 l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5zM662 6q-13 2 -20.5 13t-5.5 24l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5z" /> +<glyph unicode="" d="M1497 709v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406 q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14z" /> +<glyph unicode="" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" /> +<glyph unicode="" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" /> +<glyph unicode="" d="M825 547l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150l323 -589v-435h134v436zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" /> +<glyph unicode="" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55q-3 39 32 59l1664 960q35 21 68 -2zM1422 26l221 1323l-1434 -827l336 -137 l863 639l-478 -797z" /> +<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298zM896 928v-448q0 -14 -9 -23 t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1682 -128q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5t45 -15 t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18q-15 10 -45 12t-53 2 t-41 14t-18 45q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1278 1347v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179 q-64 117 -64 259q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43z" /> +<glyph unicode="" d="M352 128v-128h-352v128h352zM704 256q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM864 640v-128h-864v128h864zM224 1152v-128h-224v128h224zM1536 128v-128h-736v128h736zM576 1280q26 0 45 -19t19 -45v-256 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1216 768q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1536 640v-128h-224v128h224zM1536 1152v-128h-864v128h864z" /> +<glyph unicode="" d="M1216 512q133 0 226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5t93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5 t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86z" /> +<glyph unicode="" d="M1280 341q0 88 -62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5t62.5 -150.5 t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5t150.5 62.5t62.5 150.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M571 947q-10 25 -34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49t35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49zM1513 1303l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5 t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68zM1521 1359q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23t9 23q10 9 23 9t23 -9l90 -91 q10 -9 10 -22.5t-10 -22.5zM1751 1129q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5t10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23zM1792 1312q0 -14 -9 -23t-23 -9h-96q-14 0 -23 9t-9 23t9 23t23 9h96q14 0 23 -9t9 -23zM1600 1504v-96q0 -14 -9 -23t-23 -9 t-23 9t-9 23v96q0 14 9 23t23 9t23 -9t9 -23zM1751 1449l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5t10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M609 720l287 208l287 -208l-109 -336h-355zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM1515 186q149 203 149 454v3l-102 -89l-240 224l63 323 l134 -12q-150 206 -389 282l53 -124l-287 -159l-287 159l53 124q-239 -76 -389 -282l135 12l62 -323l-240 -224l-102 89v-3q0 -251 149 -454l30 132l326 -40l139 -298l-116 -69q117 -39 240 -39t240 39l-116 69l139 298l326 40z" /> +<glyph unicode="" horiz-adv-x="1792" d="M448 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM256 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM832 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM66 768q-28 0 -47 19t-19 46v129h514v-129q0 -27 -19 -46t-46 -19h-383zM1216 224v-192q0 -14 -9 -23t-23 -9h-192 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1600 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23 zM1408 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1016v-13h-514v10q0 104 -382 102q-382 -1 -382 -102v-10h-514v13q0 17 8.5 43t34 64t65.5 75.5t110.5 76t160 67.5t224 47.5t293.5 18.5t293 -18.5t224 -47.5 t160.5 -67.5t110.5 -76t65.5 -75.5t34 -64t8.5 -43zM1792 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 962v-129q0 -27 -19 -46t-46 -19h-384q-27 0 -46 19t-19 46v129h514z" /> +<glyph unicode="" horiz-adv-x="1792" d="M704 1216v-768q0 -26 -19 -45t-45 -19v-576q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v512l249 873q7 23 31 23h424zM1024 1216v-704h-256v704h256zM1792 320v-512q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v576q-26 0 -45 19t-19 45v768h424q24 0 31 -23z M736 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23zM1408 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1755 1083q37 -37 37 -90t-37 -91l-401 -400l150 -150l-160 -160q-163 -163 -389.5 -186.5t-411.5 100.5l-362 -362h-181v181l362 362q-124 185 -100.5 411.5t186.5 389.5l160 160l150 -150l400 401q38 37 91 37t90 -37t37 -90.5t-37 -90.5l-400 -401l234 -234l401 400 q38 37 91 37t90 -37z" /> +<glyph unicode="" horiz-adv-x="1792" d="M873 796q0 -83 -63.5 -142.5t-152.5 -59.5t-152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59t152.5 -59t63.5 -143zM1375 796q0 -83 -63 -142.5t-153 -59.5q-89 0 -152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59q90 0 153 -59t63 -143zM1600 616v667q0 87 -32 123.5 t-111 36.5h-1112q-83 0 -112.5 -34t-29.5 -126v-673q43 -23 88.5 -40t81 -28t81 -18.5t71 -11t70 -4t58.5 -0.5t56.5 2t44.5 2q68 1 95 -27q6 -6 10 -9q26 -25 61 -51q7 91 118 87q5 0 36.5 -1.5t43 -2t45.5 -1t53 1t54.5 4.5t61 8.5t62 13.5t67 19.5t67.5 27t72 34.5z M1763 621q-121 -149 -372 -252q84 -285 -23 -465q-66 -113 -183 -148q-104 -32 -182 15q-86 51 -82 164l-1 326v1q-8 2 -24.5 6t-23.5 5l-1 -338q4 -114 -83 -164q-79 -47 -183 -15q-117 36 -182 150q-105 180 -22 463q-251 103 -372 252q-25 37 -4 63t60 -1q3 -2 11 -7 t11 -8v694q0 72 47 123t114 51h1257q67 0 114 -51t47 -123v-694l21 15q39 27 60 1t-4 -63z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1102v-434h-145v434h145zM1294 1102v-434h-145v434h145zM1294 342l253 254v795h-1194v-1049h326v-217l217 217h398zM1692 1536v-1013l-434 -434h-326l-217 -217h-217v217h-398v1158l109 289h1483z" /> +<glyph unicode="" d="M773 217v-127q-1 -292 -6 -305q-12 -32 -51 -40q-54 -9 -181.5 38t-162.5 89q-13 15 -17 36q-1 12 4 26q4 10 34 47t181 216q1 0 60 70q15 19 39.5 24.5t49.5 -3.5q24 -10 37.5 -29t12.5 -42zM624 468q-3 -55 -52 -70l-120 -39q-275 -88 -292 -88q-35 2 -54 36 q-12 25 -17 75q-8 76 1 166.5t30 124.5t56 32q13 0 202 -77q70 -29 115 -47l84 -34q23 -9 35.5 -30.5t11.5 -48.5zM1450 171q-7 -54 -91.5 -161t-135.5 -127q-37 -14 -63 7q-14 10 -184 287l-47 77q-14 21 -11.5 46t19.5 46q35 43 83 26q1 -1 119 -40q203 -66 242 -79.5 t47 -20.5q28 -22 22 -61zM778 803q5 -102 -54 -122q-58 -17 -114 71l-378 598q-8 35 19 62q41 43 207.5 89.5t224.5 31.5q40 -10 49 -45q3 -18 22 -305.5t24 -379.5zM1440 695q3 -39 -26 -59q-15 -10 -329 -86q-67 -15 -91 -23l1 2q-23 -6 -46 4t-37 32q-30 47 0 87 q1 1 75 102q125 171 150 204t34 39q28 19 65 2q48 -23 123 -133.5t81 -167.5v-3z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" /> +<glyph unicode="" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1112 1090q0 159 -237 159h-70q-32 0 -59.5 -21.5t-34.5 -52.5l-63 -276q-2 -5 -2 -16q0 -24 17 -39.5t41 -15.5h53q69 0 128.5 13t112.5 41t83.5 81.5t30.5 126.5zM1716 938q0 -265 -220 -428q-219 -161 -612 -161h-61q-32 0 -59 -21.5t-34 -52.5l-73 -316 q-8 -36 -40.5 -61.5t-69.5 -25.5h-213q-31 0 -53 20t-22 51q0 10 13 65h151q34 0 64 23.5t38 56.5l73 316q8 33 37.5 57t63.5 24h61q390 0 607 160t217 421q0 129 -51 207q183 -92 183 -335zM1533 1123q0 -264 -221 -428q-218 -161 -612 -161h-60q-32 0 -59.5 -22t-34.5 -53 l-73 -315q-8 -36 -40 -61.5t-69 -25.5h-214q-31 0 -52.5 19.5t-21.5 51.5q0 8 2 20l300 1301q8 36 40.5 61.5t69.5 25.5h444q68 0 125 -4t120.5 -15t113.5 -30t96.5 -50.5t77.5 -74t49.5 -103.5t18.5 -136z" /> +<glyph unicode="" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" /> +<glyph unicode="" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" /> +<glyph unicode="" horiz-adv-x="2304" d="M322 689h-15q-19 0 -19 18q0 28 19 85q5 15 15 19.5t28 4.5q77 0 77 -49q0 -41 -30.5 -59.5t-74.5 -18.5zM664 528q-47 0 -47 29q0 62 123 62l3 -3q-5 -88 -79 -88zM1438 687h-15q-19 0 -19 19q0 28 19 85q5 15 14.5 19t28.5 4q77 0 77 -49q0 -41 -30.5 -59.5 t-74.5 -18.5zM1780 527q-47 0 -47 30q0 62 123 62l3 -3q-5 -89 -79 -89zM373 894h-128q-8 0 -14.5 -4t-8.5 -7.5t-7 -12.5q-3 -7 -45 -190t-42 -192q0 -7 5.5 -12.5t13.5 -5.5h62q25 0 32.5 34.5l15 69t32.5 34.5q47 0 87.5 7.5t80.5 24.5t63.5 52.5t23.5 84.5 q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM719 798q-38 0 -74 -6q-2 0 -8.5 -1t-9 -1.5l-7.5 -1.5t-7.5 -2t-6.5 -3t-6.5 -4t-5 -5t-4.5 -7t-4 -9q-9 -29 -9 -39t9 -10q5 0 21.5 5t19.5 6q30 8 58 8q74 0 74 -36q0 -11 -10 -14q-8 -2 -18 -3t-21.5 -1.5t-17.5 -1.5 q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5q0 -38 26 -59.5t64 -21.5q24 0 45.5 6.5t33 13t38.5 23.5q-3 -7 -3 -15t5.5 -13.5t12.5 -5.5h56q1 1 7 3.5t7.5 3.5t5 3.5t5 5.5t2.5 8l45 194q4 13 4 30q0 81 -145 81zM1247 793h-74q-22 0 -39 -23q-5 -7 -29.5 -51 t-46.5 -81.5t-26 -38.5l-5 4q0 77 -27 166q-1 5 -3.5 8.5t-6 6.5t-6.5 5t-8.5 3t-8.5 1.5t-9.5 1t-9 0.5h-10h-8.5q-38 0 -38 -21l1 -5q5 -53 25 -151t25 -143q2 -16 2 -24q0 -19 -30.5 -61.5t-30.5 -58.5q0 -13 40 -13q61 0 76 25l245 415q10 20 10 26q0 9 -8 9zM1489 892 h-129q-18 0 -29 -23q-6 -13 -46.5 -191.5t-40.5 -190.5q0 -20 43 -20h7.5h9h9t9.5 1t8.5 2t8.5 3t6.5 4.5t5.5 6t3 8.5l21 91q2 10 10.5 17t19.5 7q47 0 87.5 7t80.5 24.5t63.5 52.5t23.5 84q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM1835 798q-26 0 -74 -6 q-38 -6 -48 -16q-7 -8 -11 -19q-8 -24 -8 -39q0 -10 8 -10q1 0 41 12q30 8 58 8q74 0 74 -36q0 -12 -10 -14q-4 -1 -57 -7q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5t26 -58.5t64 -21.5q24 0 45 6t34 13t38 24q-3 -15 -3 -16q0 -5 2 -8.5t6.5 -5.5t8 -3.5 t10.5 -2t9.5 -0.5h9.5h8q42 0 48 25l45 194q3 15 3 31q0 81 -145 81zM2157 889h-55q-25 0 -33 -40q-10 -44 -36.5 -167t-42.5 -190v-5q0 -16 16 -18h1h57q10 0 18.5 6.5t10.5 16.5l83 374h-1l1 5q0 7 -5.5 12.5t-13.5 5.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048 q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" /> +<glyph unicode="" horiz-adv-x="1408" d="M512 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM768 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1024 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167 q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1150 462v-109q0 -50 -36.5 -89t-94 -60.5t-118 -32.5t-117.5 -11q-205 0 -342.5 139t-137.5 346q0 203 136 339t339 136q34 0 75.5 -4.5t93 -18t92.5 -34t69 -56.5t28 -81v-109q0 -16 -16 -16h-118q-16 0 -16 16v70q0 43 -65.5 67.5t-137.5 24.5q-140 0 -228.5 -91.5 t-88.5 -237.5q0 -151 91.5 -249.5t233.5 -98.5q68 0 138 24t70 66v70q0 7 4.5 11.5t10.5 4.5h119q6 0 11 -4.5t5 -11.5zM768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M972 761q0 108 -53.5 169t-147.5 61q-63 0 -124 -30.5t-110 -84.5t-79.5 -137t-30.5 -180q0 -112 53.5 -173t150.5 -61q96 0 176 66.5t122.5 166t42.5 203.5zM1536 640q0 -111 -37 -197t-98.5 -135t-131.5 -74.5t-145 -27.5q-6 0 -15.5 -0.5t-16.5 -0.5q-95 0 -142 53 q-28 33 -33 83q-52 -66 -131.5 -110t-173.5 -44q-161 0 -249.5 95.5t-88.5 269.5q0 157 66 290t179 210.5t246 77.5q87 0 155 -35.5t106 -99.5l2 19l11 56q1 6 5.5 12t9.5 6h118q5 0 13 -11q5 -5 3 -16l-120 -614q-5 -24 -5 -48q0 -39 12.5 -52t44.5 -13q28 1 57 5.5t73 24 t77 50t57 89.5t24 137q0 292 -174 466t-466 174q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51q228 0 405 144q11 9 24 8t21 -12l41 -49q8 -12 7 -24q-2 -13 -12 -22q-102 -83 -227.5 -128t-258.5 -45q-156 0 -298 61 t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q344 0 556 -212t212 -556z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1698 1442q94 -94 94 -226.5t-94 -225.5l-225 -223l104 -104q10 -10 10 -23t-10 -23l-210 -210q-10 -10 -23 -10t-23 10l-105 105l-603 -603q-37 -37 -90 -37h-203l-256 -128l-64 64l128 256v203q0 53 37 90l603 603l-105 105q-10 10 -10 23t10 23l210 210q10 10 23 10 t23 -10l104 -104l223 225q93 94 225.5 94t226.5 -94zM512 64l576 576l-192 192l-576 -576v-192h192z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 128v-384h-1792v384q45 0 85 14t59 27.5t47 37.5q30 27 51.5 38t56.5 11t55.5 -11t52.5 -38q29 -25 47 -38t58 -27t86 -14q45 0 85 14.5t58 27t48 37.5q21 19 32.5 27t31 15t43.5 7q35 0 56.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14t85 14t59 27.5t47 37.5 q30 27 51.5 38t56.5 11q34 0 55.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14zM1792 448v-192q-35 0 -55.5 11t-52.5 38q-29 25 -47 38t-58 27t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-22 -19 -33 -27t-31 -15t-44 -7q-35 0 -56.5 11t-51.5 38q-29 25 -47 38t-58 27 t-86 14q-45 0 -85 -14.5t-58 -27t-48 -37.5q-21 -19 -32.5 -27t-31 -15t-43.5 -7q-35 0 -56.5 11t-51.5 38q-28 24 -47 37.5t-59 27.5t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-30 -27 -51.5 -38t-56.5 -11v192q0 80 56 136t136 56h64v448h256v-448h256v448h256v-448h256v448 h256v-448h64q80 0 136 -56t56 -136zM512 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1024 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51 t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1536 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" /> +<glyph unicode="" horiz-adv-x="1792" d="M768 646l546 -546q-106 -108 -247.5 -168t-298.5 -60q-209 0 -385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103v-762zM955 640h773q0 -157 -60 -298.5t-168 -247.5zM1664 768h-768v768q209 0 385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1920 1248v-435q0 -21 -19.5 -29.5t-35.5 7.5l-121 121l-633 -633q-10 -10 -23 -10t-23 10l-233 233l-416 -416l-192 192l585 585q10 10 23 10t23 -10l233 -233l464 464l-121 121q-16 16 -7.5 35.5t29.5 19.5h435q14 0 23 -9 t9 -23z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1292 832q0 -6 10 -41q10 -29 25 -49.5t41 -34t44 -20t55 -16.5q325 -91 325 -332q0 -146 -105.5 -242.5t-254.5 -96.5q-59 0 -111.5 18.5t-91.5 45.5t-77 74.5t-63 87.5t-53.5 103.5t-43.5 103t-39.5 106.5t-35.5 95q-32 81 -61.5 133.5t-73.5 96.5t-104 64t-142 20 q-96 0 -183 -55.5t-138 -144.5t-51 -185q0 -160 106.5 -279.5t263.5 -119.5q177 0 258 95q56 63 83 116l84 -152q-15 -34 -44 -70l1 -1q-131 -152 -388 -152q-147 0 -269.5 79t-190.5 207.5t-68 274.5q0 105 43.5 206t116 176.5t172 121.5t204.5 46q87 0 159 -19t123.5 -50 t95 -80t72.5 -99t58.5 -117t50.5 -124.5t50 -130.5t55 -127q96 -200 233 -200q81 0 138.5 48.5t57.5 128.5q0 42 -19 72t-50.5 46t-72.5 31.5t-84.5 27t-87.5 34t-81 52t-65 82t-39 122.5q-3 16 -3 33q0 110 87.5 192t198.5 78q78 -3 120.5 -14.5t90.5 -53.5h-1 q12 -11 23 -24.5t26 -36t19 -27.5l-129 -99q-26 49 -54 70v1q-23 21 -97 21q-49 0 -84 -33t-35 -83z" /> +<glyph unicode="" d="M1432 484q0 173 -234 239q-35 10 -53 16.5t-38 25t-29 46.5q0 2 -2 8.5t-3 12t-1 7.5q0 36 24.5 59.5t60.5 23.5q54 0 71 -15h-1q20 -15 39 -51l93 71q-39 54 -49 64q-33 29 -67.5 39t-85.5 10q-80 0 -142 -57.5t-62 -137.5q0 -7 2 -23q16 -96 64.5 -140t148.5 -73 q29 -8 49 -15.5t45 -21.5t38.5 -34.5t13.5 -46.5v-5q1 -58 -40.5 -93t-100.5 -35q-97 0 -167 144q-23 47 -51.5 121.5t-48 125.5t-54 110.5t-74 95.5t-103.5 60.5t-147 24.5q-101 0 -192 -56t-144 -148t-50 -192v-1q4 -108 50.5 -199t133.5 -147.5t196 -56.5q186 0 279 110 q20 27 31 51l-60 109q-42 -80 -99 -116t-146 -36q-115 0 -191 87t-76 204q0 105 82 189t186 84q112 0 170 -53.5t104 -172.5q8 -21 25.5 -68.5t28.5 -76.5t31.5 -74.5t38.5 -74t45.5 -62.5t55.5 -53.5t66 -33t80 -13.5q107 0 183 69.5t76 174.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1152 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1920 640q0 104 -40.5 198.5 t-109.5 163.5t-163.5 109.5t-198.5 40.5h-386q119 -90 188.5 -224t69.5 -288t-69.5 -288t-188.5 -224h386q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM2048 640q0 -130 -51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5 t-136.5 204t-51 248.5t51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M0 640q0 130 51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5t-51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5t-136.5 204t-51 248.5zM1408 128q104 0 198.5 40.5t163.5 109.5 t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M762 384h-314q-40 0 -57.5 35t6.5 67l188 251q-65 31 -137 31q-132 0 -226 -94t-94 -226t94 -226t226 -94q115 0 203 72.5t111 183.5zM576 512h186q-18 85 -75 148zM1056 512l288 384h-480l-99 -132q105 -103 126 -252h165zM2176 448q0 132 -94 226t-226 94 q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94t226 94t94 226zM2304 448q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 97 39.5 183.5t109.5 149.5l-65 98l-353 -469 q-18 -26 -51 -26h-197q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q114 0 215 -55l137 183h-224q-26 0 -45 19t-19 45t19 45t45 19h384v-128h435l-85 128h-222q-26 0 -45 19t-19 45t19 45t45 19h256q33 0 53 -28l267 -400 q91 44 192 44q185 0 316.5 -131.5t131.5 -316.5z" /> +<glyph unicode="" d="M384 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1362 716l-72 384q-5 23 -22.5 37.5t-40.5 14.5 h-918q-23 0 -40.5 -14.5t-22.5 -37.5l-72 -384q-5 -30 14 -53t49 -23h1062q30 0 49 23t14 53zM1136 1328q0 20 -14 34t-34 14h-640q-20 0 -34 -14t-14 -34t14 -34t34 -14h640q20 0 34 14t14 34zM1536 603v-603h-128v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5v128h-768v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5v128h-128v603q0 112 25 223l103 454q9 78 97.5 137t230 89t312.5 30t312.5 -30t230 -89t97.5 -137l105 -454q23 -102 23 -223z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1463 704q0 -35 -25 -60.5t-61 -25.5h-702q-36 0 -61 25.5t-25 60.5t25 60.5t61 25.5h702q36 0 61 -25.5t25 -60.5zM1677 704q0 86 -23 170h-982q-36 0 -61 25t-25 60q0 36 25 61t61 25h908q-88 143 -235 227t-320 84q-177 0 -327.5 -87.5t-238 -237.5t-87.5 -327 q0 -86 23 -170h982q36 0 61 -25t25 -60q0 -36 -25 -61t-61 -25h-908q88 -143 235.5 -227t320.5 -84q132 0 253 51.5t208 139t139 208t52 253.5zM2048 959q0 -35 -25 -60t-61 -25h-131q17 -85 17 -170q0 -167 -65.5 -319.5t-175.5 -263t-262.5 -176t-319.5 -65.5 q-246 0 -448.5 133t-301.5 350h-189q-36 0 -61 25t-25 61q0 35 25 60t61 25h132q-17 85 -17 170q0 167 65.5 319.5t175.5 263t262.5 176t320.5 65.5q245 0 447.5 -133t301.5 -350h188q36 0 61 -25t25 -61z" /> +<glyph unicode="" horiz-adv-x="1280" d="M953 1158l-114 -328l117 -21q165 451 165 518q0 56 -38 56q-57 0 -130 -225zM654 471l33 -88q37 42 71 67l-33 5.5t-38.5 7t-32.5 8.5zM362 1367q0 -98 159 -521q18 10 49 10q15 0 75 -5l-121 351q-75 220 -123 220q-19 0 -29 -17.5t-10 -37.5zM283 608q0 -36 51.5 -119 t117.5 -153t100 -70q14 0 25.5 13t11.5 27q0 24 -32 102q-13 32 -32 72t-47.5 89t-61.5 81t-62 32q-20 0 -45.5 -27t-25.5 -47zM125 273q0 -41 25 -104q59 -145 183.5 -227t281.5 -82q227 0 382 170q152 169 152 427q0 43 -1 67t-11.5 62t-30.5 56q-56 49 -211.5 75.5 t-270.5 26.5q-37 0 -49 -11q-12 -5 -12 -35q0 -34 21.5 -60t55.5 -40t77.5 -23.5t87.5 -11.5t85 -4t70 0h23q24 0 40 -19q15 -19 19 -55q-28 -28 -96 -54q-61 -22 -93 -46q-64 -46 -108.5 -114t-44.5 -137q0 -31 18.5 -88.5t18.5 -87.5l-3 -12q-4 -12 -4 -14 q-137 10 -146 216q-8 -2 -41 -2q2 -7 2 -21q0 -53 -40.5 -89.5t-94.5 -36.5q-82 0 -166.5 78t-84.5 159q0 34 33 67q52 -64 60 -76q77 -104 133 -104q12 0 26.5 8.5t14.5 20.5q0 34 -87.5 145t-116.5 111q-43 0 -70 -44.5t-27 -90.5zM11 264q0 101 42.5 163t136.5 88 q-28 74 -28 104q0 62 61 123t122 61q29 0 70 -15q-163 462 -163 567q0 80 41 130.5t119 50.5q131 0 325 -581q6 -17 8 -23q6 16 29 79.5t43.5 118.5t54 127.5t64.5 123t70.5 86.5t76.5 36q71 0 112 -49t41 -122q0 -108 -159 -550q61 -15 100.5 -46t58.5 -78t26 -93.5 t7 -110.5q0 -150 -47 -280t-132 -225t-211 -150t-278 -55q-111 0 -223 42q-149 57 -258 191.5t-109 286.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" /> +<glyph unicode="" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> +<glyph unicode="" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" /> +<glyph unicode="" d="M915 450h-294l147 551zM1001 128h311l-324 1024h-440l-324 -1024h311l383 314zM1536 1120v-960q0 -118 -85 -203t-203 -85h-960q-118 0 -203 85t-85 203v960q0 118 85 203t203 85h960q118 0 203 -85t85 -203z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 641q0 -21 -13 -36.5t-33 -19.5l-205 -356q3 -9 3 -18q0 -20 -12.5 -35.5t-32.5 -19.5l-193 -337q3 -8 3 -16q0 -23 -16.5 -40t-40.5 -17q-25 0 -41 18h-400q-17 -20 -43 -20t-43 20h-399q-17 -20 -43 -20q-23 0 -40 16.5t-17 40.5q0 8 4 20l-193 335 q-20 4 -32.5 19.5t-12.5 35.5q0 9 3 18l-206 356q-20 5 -32.5 20.5t-12.5 35.5q0 21 13.5 36.5t33.5 19.5l199 344q0 1 -0.5 3t-0.5 3q0 36 34 51l209 363q-4 10 -4 18q0 24 17 40.5t40 16.5q26 0 44 -21h396q16 21 43 21t43 -21h398q18 21 44 21q23 0 40 -16.5t17 -40.5 q0 -6 -4 -18l207 -358q23 -1 39 -17.5t16 -38.5q0 -13 -7 -27l187 -324q19 -4 31.5 -19.5t12.5 -35.5zM1063 -158h389l-342 354h-143l-342 -354h360q18 16 39 16t39 -16zM112 654q1 -4 1 -13q0 -10 -2 -15l208 -360q2 0 4.5 -1t5.5 -2.5l5 -2.5l188 199v347l-187 194 q-13 -8 -29 -10zM986 1438h-388l190 -200l554 200h-280q-16 -16 -38 -16t-38 16zM1689 226q1 6 5 11l-64 68l-17 -79h76zM1583 226l22 105l-252 266l-296 -307l63 -64h463zM1495 -142l16 28l65 310h-427l333 -343q8 4 13 5zM578 -158h5l342 354h-373v-335l4 -6q14 -5 22 -13 zM552 226h402l64 66l-309 321l-157 -166v-221zM359 226h163v189l-168 -177q4 -8 5 -12zM358 1051q0 -1 0.5 -2t0.5 -2q0 -16 -8 -29l171 -177v269zM552 1121v-311l153 -157l297 314l-223 236zM556 1425l-4 -8v-264l205 74l-191 201q-6 -2 -10 -3zM1447 1438h-16l-621 -224 l213 -225zM1023 946l-297 -315l311 -319l296 307zM688 634l-136 141v-284zM1038 270l-42 -44h85zM1374 618l238 -251l132 624l-3 5l-1 1zM1718 1018q-8 13 -8 29v2l-216 376q-5 1 -13 5l-437 -463l310 -327zM522 1142v223l-163 -282zM522 196h-163l163 -283v283zM1607 196 l-48 -227l130 227h-82zM1729 266l207 361q-2 10 -2 14q0 1 3 16l-171 296l-129 -612l77 -82q5 3 15 7z" /> +<glyph unicode="" d="M0 856q0 131 91.5 226.5t222.5 95.5h742l352 358v-1470q0 -132 -91.5 -227t-222.5 -95h-780q-131 0 -222.5 95t-91.5 227v790zM1232 102l-176 180v425q0 46 -32 79t-78 33h-484q-46 0 -78 -33t-32 -79v-492q0 -46 32.5 -79.5t77.5 -33.5h770z" /> +<glyph unicode="" d="M934 1386q-317 -121 -556 -362.5t-358 -560.5q-20 89 -20 176q0 208 102.5 384.5t278.5 279t384 102.5q82 0 169 -19zM1203 1267q93 -65 164 -155q-389 -113 -674.5 -400.5t-396.5 -676.5q-93 72 -155 162q112 386 395 671t667 399zM470 -67q115 356 379.5 622t619.5 384 q40 -92 54 -195q-292 -120 -516 -345t-343 -518q-103 14 -194 52zM1536 -125q-193 50 -367 115q-135 -84 -290 -107q109 205 274 370.5t369 275.5q-21 -152 -101 -284q65 -175 115 -370z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1893 1144l155 -1272q-131 0 -257 57q-200 91 -393 91q-226 0 -374 -148q-148 148 -374 148q-193 0 -393 -91q-128 -57 -252 -57h-5l155 1272q224 127 482 127q233 0 387 -106q154 106 387 106q258 0 482 -127zM1398 157q129 0 232 -28.5t260 -93.5l-124 1021 q-171 78 -368 78q-224 0 -374 -141q-150 141 -374 141q-197 0 -368 -78l-124 -1021q105 43 165.5 65t148.5 39.5t178 17.5q202 0 374 -108q172 108 374 108zM1438 191l-55 907q-211 -4 -359 -155q-152 155 -374 155q-176 0 -336 -66l-114 -941q124 51 228.5 76t221.5 25 q209 0 374 -102q172 107 374 102z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1500 165v733q0 21 -15 36t-35 15h-93q-20 0 -35 -15t-15 -36v-733q0 -20 15 -35t35 -15h93q20 0 35 15t15 35zM1216 165v531q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-531q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM924 165v429q0 20 -15 35t-35 15h-101 q-20 0 -35 -15t-15 -35v-429q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM632 165v362q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-362q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM2048 311q0 -166 -118 -284t-284 -118h-1244q-166 0 -284 118t-118 284 q0 116 63 214.5t168 148.5q-10 34 -10 73q0 113 80.5 193.5t193.5 80.5q102 0 180 -67q45 183 194 300t338 117q149 0 275 -73.5t199.5 -199.5t73.5 -275q0 -66 -14 -122q135 -33 221 -142.5t86 -247.5z" /> +<glyph unicode="" d="M0 1536h1536v-1392l-776 -338l-760 338v1392zM1436 209v926h-1336v-926l661 -294zM1436 1235v201h-1336v-201h1336zM181 937v-115h-37v115h37zM181 789v-115h-37v115h37zM181 641v-115h-37v115h37zM181 493v-115h-37v115h37zM181 345v-115h-37v115h37zM207 202l15 34 l105 -47l-15 -33zM343 142l15 34l105 -46l-15 -34zM478 82l15 34l105 -46l-15 -34zM614 23l15 33l104 -46l-15 -34zM797 10l105 46l15 -33l-105 -47zM932 70l105 46l15 -34l-105 -46zM1068 130l105 46l15 -34l-105 -46zM1203 189l105 47l15 -34l-105 -46zM259 1389v-36h-114 v36h114zM421 1389v-36h-115v36h115zM583 1389v-36h-115v36h115zM744 1389v-36h-114v36h114zM906 1389v-36h-114v36h114zM1068 1389v-36h-115v36h115zM1230 1389v-36h-115v36h115zM1391 1389v-36h-114v36h114zM181 1049v-79h-37v115h115v-36h-78zM421 1085v-36h-115v36h115z M583 1085v-36h-115v36h115zM744 1085v-36h-114v36h114zM906 1085v-36h-114v36h114zM1068 1085v-36h-115v36h115zM1230 1085v-36h-115v36h115zM1355 970v79h-78v36h115v-115h-37zM1355 822v115h37v-115h-37zM1355 674v115h37v-115h-37zM1355 526v115h37v-115h-37zM1355 378 v115h37v-115h-37zM1355 230v115h37v-115h-37zM760 265q-129 0 -221 91.5t-92 221.5q0 129 92 221t221 92q130 0 221.5 -92t91.5 -221q0 -130 -91.5 -221.5t-221.5 -91.5zM595 646q0 -36 19.5 -56.5t49.5 -25t64 -7t64 -2t49.5 -9t19.5 -30.5q0 -49 -112 -49q-97 0 -123 51 h-3l-31 -63q67 -42 162 -42q29 0 56.5 5t55.5 16t45.5 33t17.5 53q0 46 -27.5 69.5t-67.5 27t-79.5 3t-67 5t-27.5 25.5q0 21 20.5 33t40.5 15t41 3q34 0 70.5 -11t51.5 -34h3l30 58q-3 1 -21 8.5t-22.5 9t-19.5 7t-22 7t-20 4.5t-24 4t-23 1q-29 0 -56.5 -5t-54 -16.5 t-43 -34t-16.5 -53.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" /> +<glyph unicode="" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M2301 500q12 -103 -22 -198.5t-99 -163.5t-158.5 -106t-196.5 -31q-161 11 -279.5 125t-134.5 274q-12 111 27.5 210.5t118.5 170.5l-71 107q-96 -80 -151 -194t-55 -244q0 -27 -18.5 -46.5t-45.5 -19.5h-256h-69q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5 t-131.5 316.5t131.5 316.5t316.5 131.5q76 0 152 -27l24 45q-123 110 -304 110h-64q-26 0 -45 19t-19 45t19 45t45 19h128q78 0 145 -13.5t116.5 -38.5t71.5 -39.5t51 -36.5h512h115l-85 128h-222q-30 0 -49 22.5t-14 52.5q4 23 23 38t43 15h253q33 0 53 -28l70 -105 l114 114q19 19 46 19h101q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-179l115 -172q131 63 275 36q143 -26 244 -134.5t118 -253.5zM448 128q115 0 203 72.5t111 183.5h-314q-35 0 -55 31q-18 32 -1 63l147 277q-47 13 -91 13q-132 0 -226 -94t-94 -226t94 -226 t226 -94zM1856 128q132 0 226 94t94 226t-94 226t-226 94q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94z" /> +<glyph unicode="" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h416q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-419 -420q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5 t431 200.5q144 12 276.5 -30.5t236.5 -129.5l419 419h-261q-14 0 -23 9t-9 23v64zM704 -128q117 0 223.5 45.5t184 123t123 184t45.5 223.5t-45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123 t223.5 -45.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1728 1536q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-229 -230l156 -156q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-156 157l-99 -100q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5 t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5t431 200.5q144 12 276.5 -30.5t236.5 -129.5l99 99l-156 156q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l156 -156l229 229h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM1280 448q0 117 -45.5 223.5t-123 184t-184 123 t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2029 685q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-131q-12 -119 -67 -226t-139 -183.5t-196.5 -121.5t-234.5 -45q-180 0 -330.5 91t-234.5 247 t-74 337q8 162 94 300t226.5 219.5t302.5 85.5q166 4 310.5 -71.5t235.5 -208.5t107 -296h131v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM640 128q104 0 198.5 40.5t163.5 109.5t109.5 163.5 t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" /> +<glyph unicode="" horiz-adv-x="1280" d="M0 939q0 108 37.5 203.5t103.5 166.5t152 123t185 78t202 26q158 0 294 -66.5t221 -193.5t85 -287q0 -96 -19 -188t-60 -177t-100 -149.5t-145 -103t-189 -38.5q-68 0 -135 32t-96 88q-10 -39 -28 -112.5t-23.5 -95t-20.5 -71t-26 -71t-32 -62.5t-46 -77.5t-62 -86.5 l-14 -5l-9 10q-15 157 -15 188q0 92 21.5 206.5t66.5 287.5t52 203q-32 65 -32 169q0 83 52 156t132 73q61 0 95 -40.5t34 -102.5q0 -66 -44 -191t-44 -187q0 -63 45 -104.5t109 -41.5q55 0 102 25t78.5 68t56 95t38 110.5t20 111t6.5 99.5q0 173 -109.5 269.5t-285.5 96.5 q-200 0 -334 -129.5t-134 -328.5q0 -44 12.5 -85t27 -65t27 -45.5t12.5 -30.5q0 -28 -15 -73t-37 -45q-2 0 -17 3q-51 15 -90.5 56t-61 94.5t-32.5 108t-11 106.5z" /> +<glyph unicode="" d="M985 562q13 0 97.5 -44t89.5 -53q2 -5 2 -15q0 -33 -17 -76q-16 -39 -71 -65.5t-102 -26.5q-57 0 -190 62q-98 45 -170 118t-148 185q-72 107 -71 194v8q3 91 74 158q24 22 52 22q6 0 18 -1.5t19 -1.5q19 0 26.5 -6.5t15.5 -27.5q8 -20 33 -88t25 -75q0 -21 -34.5 -57.5 t-34.5 -46.5q0 -7 5 -15q34 -73 102 -137q56 -53 151 -101q12 -7 22 -7q15 0 54 48.5t52 48.5zM782 32q127 0 243.5 50t200.5 134t134 200.5t50 243.5t-50 243.5t-134 200.5t-200.5 134t-243.5 50t-243.5 -50t-200.5 -134t-134 -200.5t-50 -243.5q0 -203 120 -368l-79 -233 l242 77q158 -104 345 -104zM782 1414q153 0 292.5 -60t240.5 -161t161 -240.5t60 -292.5t-60 -292.5t-161 -240.5t-240.5 -161t-292.5 -60q-195 0 -365 94l-417 -134l136 405q-108 178 -108 389q0 153 60 292.5t161 240.5t240.5 161t292.5 60z" /> +<glyph unicode="" horiz-adv-x="1792" d="M128 128h1024v128h-1024v-128zM128 640h1024v128h-1024v-128zM1696 192q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM128 1152h1024v128h-1024v-128zM1696 704q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1696 1216 q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1792 384v-384h-1792v384h1792zM1792 896v-384h-1792v384h1792zM1792 1408v-384h-1792v384h1792z" /> +<glyph unicode="" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1664 512h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5v352h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352zM928 288q0 -52 38 -90t90 -38h256v-238q-68 -50 -171 -50h-874q-121 0 -194 69t-73 190q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q79 -61 154.5 -91.5t164.5 -30.5t164.5 30.5t154.5 91.5q20 17 39 17q132 0 217 -96h-223q-52 0 -90 -38t-38 -90v-192z" /> +<glyph unicode="" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1781 320l249 -249q9 -9 9 -23q0 -13 -9 -22l-136 -136q-9 -9 -22 -9q-14 0 -23 9l-249 249l-249 -249q-9 -9 -23 -9q-13 0 -22 9l-136 136 q-9 9 -9 22q0 14 9 23l249 249l-249 249q-9 9 -9 23q0 13 9 22l136 136q9 9 22 9q14 0 23 -9l249 -249l249 249q9 9 23 9q13 0 22 -9l136 -136q9 -9 9 -22q0 -14 -9 -23zM1283 320l-181 -181q-37 -37 -37 -91q0 -53 37 -90l83 -83q-21 -3 -44 -3h-874q-121 0 -194 69 t-73 190q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q154 -122 319 -122t319 122q20 17 39 17q28 0 57 -6q-28 -27 -41 -50t-13 -56q0 -54 37 -91z" /> +<glyph unicode="" horiz-adv-x="2048" d="M256 512h1728q26 0 45 -19t19 -45v-448h-256v256h-1536v-256h-256v1216q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-704zM832 832q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM2048 576v64q0 159 -112.5 271.5t-271.5 112.5h-704 q-26 0 -45 -19t-19 -45v-384h1152z" /> +<glyph unicode="" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" /> +<glyph unicode="" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" /> +<glyph unicode="" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 204v-209h-642v209h134v926h-6l-314 -1135h-243l-310 1135h-8v-926h135v-209h-538v209h69q21 0 43 19.5t22 37.5v881q0 18 -22 40t-43 22h-69v209h672l221 -821h6l223 821h670v-209h-71q-19 0 -41 -22t-22 -40v-881q0 -18 21.5 -37.5t41.5 -19.5h71z" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +</font> +</defs></svg>
\ No newline at end of file diff --git a/openecomp-ui/resources/fonts/fontawesome-webfont.ttf b/openecomp-ui/resources/fonts/fontawesome-webfont.ttf Binary files differnew file mode 100644 index 0000000000..ed9372f8ea --- /dev/null +++ b/openecomp-ui/resources/fonts/fontawesome-webfont.ttf diff --git a/openecomp-ui/resources/fonts/fontawesome-webfont.woff b/openecomp-ui/resources/fonts/fontawesome-webfont.woff Binary files differnew file mode 100644 index 0000000000..8b280b98fa --- /dev/null +++ b/openecomp-ui/resources/fonts/fontawesome-webfont.woff diff --git a/openecomp-ui/resources/fonts/fontawesome-webfont.woff2 b/openecomp-ui/resources/fonts/fontawesome-webfont.woff2 Binary files differnew file mode 100644 index 0000000000..3311d58514 --- /dev/null +++ b/openecomp-ui/resources/fonts/fontawesome-webfont.woff2 diff --git a/openecomp-ui/resources/fonts/omnes-att-bold-italic.otf b/openecomp-ui/resources/fonts/omnes-att-bold-italic.otf Binary files differnew file mode 100644 index 0000000000..77f0dbc15f --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-bold-italic.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-bold.otf b/openecomp-ui/resources/fonts/omnes-att-bold.otf Binary files differnew file mode 100644 index 0000000000..136afca84c --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-bold.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-italic.otf b/openecomp-ui/resources/fonts/omnes-att-italic.otf Binary files differnew file mode 100644 index 0000000000..5dc1da79d4 --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-italic.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-light-Italic.otf b/openecomp-ui/resources/fonts/omnes-att-light-Italic.otf Binary files differnew file mode 100644 index 0000000000..b13ae4fede --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-light-Italic.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-light.otf b/openecomp-ui/resources/fonts/omnes-att-light.otf Binary files differnew file mode 100644 index 0000000000..587d871e6f --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-light.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-medium-italic.otf b/openecomp-ui/resources/fonts/omnes-att-medium-italic.otf Binary files differnew file mode 100644 index 0000000000..f824bc23e7 --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-medium-italic.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-medium.otf b/openecomp-ui/resources/fonts/omnes-att-medium.otf Binary files differnew file mode 100644 index 0000000000..3085c1fa39 --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-medium.otf diff --git a/openecomp-ui/resources/fonts/omnes-att-regular.otf b/openecomp-ui/resources/fonts/omnes-att-regular.otf Binary files differnew file mode 100644 index 0000000000..a1a78eb7ca --- /dev/null +++ b/openecomp-ui/resources/fonts/omnes-att-regular.otf diff --git a/openecomp-ui/resources/images/ecomp/ASDC_Sprite.png b/openecomp-ui/resources/images/ecomp/ASDC_Sprite.png Binary files differnew file mode 100644 index 0000000000..db4440427c --- /dev/null +++ b/openecomp-ui/resources/images/ecomp/ASDC_Sprite.png diff --git a/openecomp-ui/resources/images/ecomp/sprite-services-icons.png b/openecomp-ui/resources/images/ecomp/sprite-services-icons.png Binary files differnew file mode 100644 index 0000000000..afb3cbc286 --- /dev/null +++ b/openecomp-ui/resources/images/ecomp/sprite-services-icons.png diff --git a/openecomp-ui/resources/images/icons/favicon.ico b/openecomp-ui/resources/images/icons/favicon.ico Binary files differnew file mode 100644 index 0000000000..c59a7aa0a3 --- /dev/null +++ b/openecomp-ui/resources/images/icons/favicon.ico diff --git a/openecomp-ui/resources/images/icons/favicon.png b/openecomp-ui/resources/images/icons/favicon.png Binary files differnew file mode 100644 index 0000000000..c59a7aa0a3 --- /dev/null +++ b/openecomp-ui/resources/images/icons/favicon.png diff --git a/openecomp-ui/resources/images/onboarding/vendor-license-model.svg b/openecomp-ui/resources/images/onboarding/vendor-license-model.svg new file mode 100644 index 0000000000..f23c30e05e --- /dev/null +++ b/openecomp-ui/resources/images/onboarding/vendor-license-model.svg @@ -0,0 +1 @@ +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}.cls-3{fill:#009fdb;}</style></defs><title>vendoe license model icon</title><path class="cls-1" d="M16,38.38h0a1.25,1.25,0,0,0-.89.38l-1.94,2a1.27,1.27,0,0,0,0,1.78,1.26,1.26,0,0,0,1.78,0l1.93-2a1.26,1.26,0,0,0,0-1.78A1.28,1.28,0,0,0,16,38.38Z"/><path class="cls-1" d="M21.89,44.21a1.26,1.26,0,0,0-.38-1,1.24,1.24,0,0,0-.87-0.35,1.26,1.26,0,0,0-.91.38l-1.94,2A1.26,1.26,0,0,0,19.62,47l1.9-2,0.43,0.33L21.55,45A1.25,1.25,0,0,0,21.89,44.21Z"/><path class="cls-1" d="M25.1,47.21a1.26,1.26,0,0,0-.84.33l-2,2.06a1.26,1.26,0,0,0,1.82,1.75l1.94-2a1.34,1.34,0,0,0,.13-0.17,1.26,1.26,0,0,0,.21-0.73A1.26,1.26,0,0,0,25.1,47.21Z"/><polygon class="cls-1" points="25.2 41.49 25.2 41.49 25.2 41.49 25.2 41.49"/><path class="cls-1" d="M11.57,34.16a1.25,1.25,0,0,0-.91.39l-1.94,2a1.26,1.26,0,0,0,1.82,1.75l1.94-2A1.26,1.26,0,0,0,11.57,34.16Z"/><polygon class="cls-2" points="25.2 41.49 25.23 41.47 25.22 41.46 25.2 41.49 25.2 41.49"/><path class="cls-3" d="M23,44.25V44.1a2.32,2.32,0,0,0-.71-1.64,2.35,2.35,0,0,0-3.31.06l-1.94,2a2.34,2.34,0,0,0,3.37,3.24l1.94-2A2.35,2.35,0,0,0,23,44.25Zm-1.46.81-1.9,2a1.26,1.26,0,0,1-1.78,0,1.27,1.27,0,0,1,0-1.78l1.94-2a1.26,1.26,0,0,1,.91-0.38,1.24,1.24,0,0,1,.87.35,1.29,1.29,0,0,1,0,1.78l0.39,0.37Z"/><path class="cls-3" d="M39.66,24.22l-3.4-2.93-5.64,5.06a5.22,5.22,0,0,1-3.33,1.33,5.09,5.09,0,0,1-2.07-.36,5.21,5.21,0,0,1-1.58-8.74l4.64-4.17-3.69-3.18a8.21,8.21,0,0,0-3.19-1.7,11.26,11.26,0,0,0-3.8-.15c-2.65.27-6.13,1.15-12.3,3.12,0,0-.83.24-0.65,0.76a0.5,0.5,0,0,0,.65.38c6.22-2,9.83-2.91,12.41-3.18a10.28,10.28,0,0,1,3.42.12A7.12,7.12,0,0,1,23.88,12l2.76,2.38-3.73,3.35a6.29,6.29,0,0,0,1.91,10.55,6.41,6.41,0,0,0,2.5.44,6.3,6.3,0,0,0,4-1.61l4.93-4.43L48.59,33.26a1.58,1.58,0,0,1,.53,1.21,2.23,2.23,0,0,1-.58,1.48,1.89,1.89,0,0,1-2.62.42l-3.82-3.3h0l-5.39-4.61A1.62,1.62,0,0,0,34,29.8a1.61,1.61,0,0,0,.56,1.11l5.53,4.77,0,0,3.76,3.24a1.58,1.58,0,0,1,.53,1.21,2.23,2.23,0,0,1-.58,1.48,2,2,0,0,1-2.17.67L32,34a1.62,1.62,0,1,0-2.11,2.45l5.89,5.08,3.44,3a1.91,1.91,0,0,1-.05,2.69,2.2,2.2,0,0,1-1.35.76,1.57,1.57,0,0,1-1.27-.35c-2.68-2.28-3.74-3.18-4.22-3.5l0,0L27,39.5a1.62,1.62,0,0,0-1.18-.39,1.61,1.61,0,0,0-1.1.56,1.62,1.62,0,0,0,0,2,1.19,1.19,0,0,0,.21.25l9.58,8.27a2,2,0,0,1-.3,2.41,1.89,1.89,0,0,1-2.62.42l-2.62-2.26a4.39,4.39,0,0,0,.66-2.4A4.46,4.46,0,0,0,25.14,44a4.93,4.93,0,0,0-.07-0.6,4.66,4.66,0,0,0-1.3-2.48,4.42,4.42,0,0,0-3.06-1.24H20.45s0-.1,0-0.14a4.48,4.48,0,0,0-4.4-4.37,4.5,4.5,0,0,0-7.55-3H8.32c-1.08,0-2.1-.1-3-0.09-0.2,0-.74,0-0.74.44S5,33,5.44,33c0.91,0,2.07.19,3.29,0.18H9L9.11,33a3.4,3.4,0,0,1,1.69-1,3.48,3.48,0,0,1,3.14.86,3.39,3.39,0,0,1,1,2.4,1.47,1.47,0,0,1,0,.18L15,36l0.39,0.33,0.38,0,0.18,0a3.5,3.5,0,0,1,2.43,1,3.4,3.4,0,0,1,1,2.37,3.49,3.49,0,0,1,0,.65L19.2,41,20,40.8a3.48,3.48,0,0,1,.6-0.07h0.12A3.41,3.41,0,0,1,24,43.57a3.3,3.3,0,0,1,.06.51,2.51,2.51,0,0,1,0,.32L24,45.19l0.68-.09a3.39,3.39,0,0,1,3.15,5.45l-0.31.4,3.38,2.91a2.59,2.59,0,0,0,1.71.62A3.29,3.29,0,0,0,35,53.31a3,3,0,0,0,.29-3.78l-9.7-8.38-0.38.32L29.37,45l5.22,4.5a2,2,0,0,1,.29.32L29.3,45l-4-3.49,0-.06h0l-0.11-.14,0.13,0.11L25.53,41a0.53,0.53,0,0,1,0-.66,0.53,0.53,0,0,1,.37-0.18,0.55,0.55,0,0,1,.39.13l5.59,4.83h0l3.93,3.33a2.63,2.63,0,0,0,2.13.6,3.31,3.31,0,0,0,2-1.16,3,3,0,0,0-.08-4.18l-9.33-8.09a0.54,0.54,0,0,1-.06-0.76,0.53,0.53,0,0,1,.56-0.16,0.55,0.55,0,0,1,.21.1L41,43.18l0.14,0.09a3.1,3.1,0,0,0,3.51-1,3.28,3.28,0,0,0,.82-2.16,2.64,2.64,0,0,0-.9-2l-3-2.57,0,0-6.31-5.44a0.54,0.54,0,0,1-.06-0.76A0.54,0.54,0,0,1,36,29.27c3.79,3.25,5.11,4.38,5.65,4.76l0,0,3.6,3.11a3,3,0,0,0,4.17-.56,3.28,3.28,0,0,0,.82-2.16,2.64,2.64,0,0,0-.9-2ZM24.24,11.63a7.68,7.68,0,0,0-3-1.58,10.73,10.73,0,0,0-3.62-.14,7.84,7.84,0,0,1,3.62.14,7.21,7.21,0,0,1,3.43,2ZM15.49,35.73v0h0Z"/><path class="cls-3" d="M15.49,35.73h0v0Z"/><path class="cls-3" d="M17.65,9.9a10.73,10.73,0,0,1,3.62.14A7.84,7.84,0,0,0,17.65,9.9Z"/><path class="cls-3" d="M34.88,49.85a2,2,0,0,0-.29-0.32L29.37,45H29.3Z"/><path class="cls-3" d="M25.25,41.54l4,3.49h0.07l-4.14-3.57,0,0Z"/><path class="cls-3" d="M25.2,41.49l0,0-0.13-.11Z"/><path class="cls-3" d="M24.24,11.63L24.7,12a7.21,7.21,0,0,0-3.43-2A7.68,7.68,0,0,1,24.24,11.63Z"/><path class="cls-3" d="M52.91,31.46c-0.69,0-1.88-.87-2.88-1.59l-0.17-.12c-0.53-.41-0.93-0.75-1.17-0.95L36.31,18.33l-7.13,6.4a3.06,3.06,0,0,1-1.95.78,2.78,2.78,0,0,1-.88-0.12,3.05,3.05,0,0,1-1.27-5.22L36.38,10a3.91,3.91,0,0,1,2.18-.95,3.48,3.48,0,0,1,1.89.64l8.25,6.76,6.75-2.82A0.5,0.5,0,0,0,55.69,13a0.55,0.55,0,0,0-.7-0.27l-6.1,2.54L41.11,8.86A4.62,4.62,0,0,0,38.53,8a5,5,0,0,0-2.87,1.22L24.36,19.37a4.11,4.11,0,0,0,1.72,7.07,4.24,4.24,0,0,0,1.18.15,4.15,4.15,0,0,0,2.64-1.05l6.43-5.78L48,29.62c0.25,0.21.66,0.56,1.23,1l0.19,0.13c1.28,0.92,2.49,1.79,3.48,1.79h0.05l0.21,0,2.06-.28a0.62,0.62,0,0,0,.53-0.53,0.57,0.57,0,0,0-.53-0.56Z"/><path class="cls-3" d="M23.52,46.75l-2,2.11a2.34,2.34,0,0,0,3.37,3.24l1.94-2a2.4,2.4,0,0,0,.65-1.67,2.32,2.32,0,0,0-.71-1.64A2.38,2.38,0,0,0,23.52,46.75Zm2.62,2.42a1.34,1.34,0,0,1-.13.17l-1.94,2a1.26,1.26,0,0,1-1.78,0,1.27,1.27,0,0,1,0-1.78l2-2.06a1.25,1.25,0,0,1,1.72,0,1.24,1.24,0,0,1,.38.88A1.26,1.26,0,0,1,26.14,49.17Z"/><path class="cls-3" d="M18.3,39.59A2.32,2.32,0,0,0,17.58,38a2.29,2.29,0,0,0-1.67-.65,2.32,2.32,0,0,0-1.64.71l-1.94,2a2.34,2.34,0,0,0,3.37,3.24l1.94-2A2.32,2.32,0,0,0,18.3,39.59Zm-1.43.92-1.93,2a1.26,1.26,0,0,1-1.78,0,1.27,1.27,0,0,1,0-1.78l1.94-2a1.25,1.25,0,0,1,.89-0.38h0a1.28,1.28,0,0,1,.87.35A1.26,1.26,0,0,1,16.87,40.51Z"/><path class="cls-3" d="M13.9,35.37h0a2.32,2.32,0,0,0-.71-1.64,2.4,2.4,0,0,0-3.31.06l-1.94,2A2.34,2.34,0,0,0,11.32,39l1.94-2A2.33,2.33,0,0,0,13.9,35.37Zm-1.43.92-1.94,2a1.26,1.26,0,0,1-1.78,0,1.27,1.27,0,0,1,0-1.78l1.94-2A1.26,1.26,0,0,1,12.48,36.29Z"/></svg>
\ No newline at end of file diff --git a/openecomp-ui/resources/images/onboarding/vendor-software-product.svg b/openecomp-ui/resources/images/onboarding/vendor-software-product.svg new file mode 100644 index 0000000000..a547c4abdf --- /dev/null +++ b/openecomp-ui/resources/images/onboarding/vendor-software-product.svg @@ -0,0 +1 @@ +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"><defs><style>.cls-1{fill:none;}.cls-2{fill:#009fdb;}</style></defs><title>vendor software product</title><rect class="cls-1" x="29" y="37.46" width="13.5" height="13.5"/><path class="cls-2" d="M39.57,44.56c0-.06,0-0.12,0-0.18s0-.11,0-0.19,0-.11,0-0.16,0-.11,0-0.17a6.81,6.81,0,0,0,1-.58,0.21,0.21,0,0,0,.08-0.22,4.6,4.6,0,0,0-.23-0.72,0.21,0.21,0,0,0-.18-0.13,6.13,6.13,0,0,0-1.19,0h0A2.07,2.07,0,0,0,38.85,42a2.72,2.72,0,0,0-.22-0.27,7,7,0,0,0,.46-1A0.21,0.21,0,0,0,39,40.4,4.72,4.72,0,0,0,38.43,40a0.21,0.21,0,0,0-.23,0,6.1,6.1,0,0,0-.93.73L37,40.6l-0.1,0-0.33-.09a7.09,7.09,0,0,0-.25-1.12,0.21,0.21,0,0,0-.19-0.15,4.86,4.86,0,0,0-.75,0,0.21,0.21,0,0,0-.18.13,6.13,6.13,0,0,0-.33,1.14l-0.35.1-0.09,0-0.23.09a6.74,6.74,0,0,0-.86-0.76,0.22,0.22,0,0,0-.23,0,5,5,0,0,0-.58.4l0,0a0.21,0.21,0,0,0-.07.23,6.24,6.24,0,0,0,.41,1.11,2.54,2.54,0,0,0-.21.26,2.22,2.22,0,0,0-.2.29h0a7.22,7.22,0,0,0-1.14-.11,0.21,0.21,0,0,0-.21.14l0,0a4.89,4.89,0,0,0-.22.67,0.22,0.22,0,0,0,.07.21,6,6,0,0,0,1,.66c0,0.06,0,.12,0,0.18s0,0.11,0,.19,0,0.1,0,.16,0,0.12,0,.17a6.81,6.81,0,0,0-1,.58,0.21,0.21,0,0,0-.08.22,4.63,4.63,0,0,0,.23.72,0.22,0.22,0,0,0,.19.14,6.22,6.22,0,0,0,1.19,0h0a2.16,2.16,0,0,0,.19.28,3,3,0,0,0,.22.27,6.86,6.86,0,0,0-.46,1,0.21,0.21,0,0,0,.06.23,4.79,4.79,0,0,0,.61.45,0.22,0.22,0,0,0,.23,0,6.15,6.15,0,0,0,.93-0.73l0.24,0.09,0.1,0,0.33,0.09a7.11,7.11,0,0,0,.25,1.12,0.21,0.21,0,0,0,.19.15l0.43,0h0.32a0.22,0.22,0,0,0,.18-0.13,6.2,6.2,0,0,0,.33-1.14l0.35-.1,0.09,0,0.23-.09a6.76,6.76,0,0,0,.86.76,0.22,0.22,0,0,0,.23,0A5.19,5.19,0,0,0,39,48.07,0.21,0.21,0,0,0,39,47.85a6.43,6.43,0,0,0-.41-1.11,2.81,2.81,0,0,0,.21-0.27,2.24,2.24,0,0,0,.2-0.29h0a7.23,7.23,0,0,0,1.14.11h0a0.27,0.27,0,0,0,.22-0.17,5,5,0,0,0,.22-0.68,0.21,0.21,0,0,0-.07-0.21A6,6,0,0,0,39.57,44.56Zm-3.82,1.69a2,2,0,1,1,2-2A2,2,0,0,1,35.75,46.26Z"/><path class="cls-2" d="M35.75,51.36a7.15,7.15,0,1,1,7.15-7.15A7.16,7.16,0,0,1,35.75,51.36Zm0-13.76a6.62,6.62,0,1,0,6.62,6.62A6.62,6.62,0,0,0,35.75,37.59Z"/><path class="cls-2" d="M32.78,27.46H4.4a0.6,0.6,0,0,1,0-1.2H32.78A0.6,0.6,0,0,1,32.78,27.46Z"/><path class="cls-2" d="M28.2,23.13a0.43,0.43,0,0,0,.1.54l4.32,3.19L28.3,30.06a0.43,0.43,0,0,0-.1.54,0.31,0.31,0,0,0,.46.12l4.77-3.52a0.42,0.42,0,0,0,0-.66L28.66,23A0.3,0.3,0,0,0,28.48,23,0.33,0.33,0,0,0,28.2,23.13Z"/><path class="cls-2" d="M54.84,27.11H37.75a0.84,0.84,0,0,1,0-1.68H54.84A0.84,0.84,0,1,1,54.84,27.11Z"/><path class="cls-2" d="M43.35,18.2a0.84,0.84,0,0,0,.26,1.16l10.85,6.91L43.6,33.18a0.84,0.84,0,0,0,.9,1.42l12-7.62a0.84,0.84,0,0,0,0-1.42l-12-7.62A0.84,0.84,0,0,0,43.35,18.2Z"/><path class="cls-2" d="M26.83,16.16H10.33a0.6,0.6,0,1,1,0-1.2h16.5A0.6,0.6,0,1,1,26.83,16.16Z"/><path class="cls-2" d="M21.52,11.84a0.39,0.39,0,0,0,.12.54l5,3.19-5,3.19a0.39,0.39,0,0,0,.42.66l5.53-3.52a0.39,0.39,0,0,0,0-.66l-5.53-3.52A0.39,0.39,0,0,0,21.52,11.84Z"/><path class="cls-2" d="M4.15,27.46a0.6,0.6,0,0,1-.53-0.87l5.7-11.3a0.6,0.6,0,1,1,1.07.54l-5.7,11.3A0.6,0.6,0,0,1,4.15,27.46Z"/><path class="cls-2" d="M26.83,38.26H10.33a0.6,0.6,0,1,1,0-1.2h16.5A0.6,0.6,0,1,1,26.83,38.26Z"/><path class="cls-2" d="M21.52,33.93a0.39,0.39,0,0,0,.12.54l5,3.19-5,3.19a0.39,0.39,0,0,0,.42.66L27.58,38a0.39,0.39,0,0,0,0-.66l-5.53-3.52A0.39,0.39,0,0,0,21.52,33.93Z"/><path class="cls-2" d="M9.85,38.26a0.59,0.59,0,0,1-.53-0.31L3.63,27.39a0.6,0.6,0,1,1,1.05-.57l5.7,10.56a0.6,0.6,0,0,1-.24.81A0.59,0.59,0,0,1,9.85,38.26Z"/></svg>
\ No newline at end of file diff --git a/openecomp-ui/resources/images/plus-circle-icon.svg b/openecomp-ui/resources/images/plus-circle-icon.svg new file mode 100644 index 0000000000..352dcad5e8 --- /dev/null +++ b/openecomp-ui/resources/images/plus-circle-icon.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Isolation_Mode" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" + y="0px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#009fdb;} +</style> +<g> + <g> + <path class="st0" d="M15.8,32.1c-8.6,0-15.6-7-15.6-15.6s7-15.6,15.6-15.6s15.6,7,15.6,15.6S24.4,32.1,15.8,32.1z M15.8,3.2 + c-7.3,0-13.3,6-13.3,13.3s6,13.3,13.3,13.3s13.3-6,13.3-13.3S23.1,3.2,15.8,3.2z"/> + </g> + <path class="st0" d="M23.8,15.7h-6.9V8.5c0-0.6-0.5-1.1-1.1-1.1c-0.6,0-1.1,0.5-1.1,1.1v0v7.3H8.8c-0.6,0-1.1,0.5-1.1,1.1 + c0,0.6,0.5,1.1,1.1,1.1c0,0,0,0,0,0h0h5.8v5.4v0c0,0,0,0,0,0c0,0.6,0.5,1.1,1.1,1.1c0.6,0,1.1-0.5,1.1-1.1V18h6.9h0 + c0.6,0,1.1-0.5,1.1-1.1C24.8,16.3,24.3,15.7,23.8,15.7z M23.8,16.5C23.8,16.5,23.8,16.5,23.8,16.5C23.8,16.5,23.8,16.5,23.8,16.5 + L23.8,16.5z M16.1,8.5C16.1,8.5,16.1,8.5,16.1,8.5C16.1,8.5,16.1,8.5,16.1,8.5L16.1,8.5z"/> +</g> +</svg> diff --git a/openecomp-ui/resources/scss/_common.scss b/openecomp-ui/resources/scss/_common.scss new file mode 100644 index 0000000000..6ade0abfe5 --- /dev/null +++ b/openecomp-ui/resources/scss/_common.scss @@ -0,0 +1,6 @@ +@import "common/variables"; +@import "common/typography"; +@import "common/base"; +@import "common/layout"; +@import "common/utils"; + diff --git a/openecomp-ui/resources/scss/_components.scss b/openecomp-ui/resources/scss/_components.scss new file mode 100644 index 0000000000..884308885a --- /dev/null +++ b/openecomp-ui/resources/scss/_components.scss @@ -0,0 +1,73 @@ +@import "components/punchOut"; +@import "components/buttons"; +@import "components/forms"; +@import "components/validationForm"; +@import "components/slidePanel"; +@import "components/dualListBox"; +@import "components/listEditorView"; +@import "components/toggleInput"; +@import "components/notifications"; +@import "components/inputOptions"; +@import "components/progressBar"; +@import "components/versionController"; +@import "components/sequenceDiagram"; +@import "components/navigationSideBar"; +@import "components/loader"; +@import "components/dropzone"; +@import "components/submitErrorResponse"; +@import "components/expandableInput"; + +%noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.clickable { + cursor: pointer; +} + +.no-padding { + padding: 0; +} + +.next-to-icon-label { + $margin: 10px; + margin-left: $margin; + &.right { + margin-left: 0; + margin-right: $margin; + } +} + +.search-wrapper { + display: flex; + .search-input-control { + flex: 1 1; + margin: 0; + .form-control { + font-style: italic; + } + } + .search-icon { + position: relative; + left: -20px; + align-self: center; + width: 0; + color: $dark-gray; + } + .filter-icon { + position: relative; + left: -20px; + align-self: center; + width: 0; + background-color: $white; + } +} + +.warning-icon { + margin-left: 50%; + color: $yellow; +} diff --git a/openecomp-ui/resources/scss/_modules.scss b/openecomp-ui/resources/scss/_modules.scss new file mode 100644 index 0000000000..fd35bf2e0b --- /dev/null +++ b/openecomp-ui/resources/scss/_modules.scss @@ -0,0 +1,22 @@ +@import "modules/licenseAgreement"; +@import "modules/featureGroup"; +@import "modules/entitlementPools"; +@import "modules/licenseKeyGroup"; +@import "modules/softwareProductLandingPage"; +@import "modules/softwareProductCreatePage"; +@import "modules/_softwareProductAttachmentPage"; +@import "modules/_softwareProductProcessesPage"; +@import "modules/_vspComponentQuestionnaire"; +@import "modules/_softwareProductNetworksPage"; +@import "modules/_softwareProductComponentNetwork"; +@import "modules/_softwareProductComponentGeneral"; +@import "modules/_softwareproductComponentLoadBalancing"; +@import "modules/vspComponentMonitoring"; +@import "modules/licenseModel"; +@import "modules/onboardingCatalog"; +@import "modules/workflows"; +@import "modules/uploadScreen"; + + + + diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_buttons.scss b/openecomp-ui/resources/scss/bootstrap-cust/_buttons.scss new file mode 100644 index 0000000000..bf58006d89 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_buttons.scss @@ -0,0 +1,13 @@ + +.btn { + height: 30px; + min-width: 95px; +} + +.btn-default .fa { + color: $brand-primary; +} + +.btn-info { + @include button-variant($text-black, $tlv-gray, $light-gray); +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_close.scss b/openecomp-ui/resources/scss/bootstrap-cust/_close.scss new file mode 100644 index 0000000000..38d7138d86 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_close.scss @@ -0,0 +1,7 @@ +// +// Close icons +// -------------------------------------------------- + +.close { + font-size: $heading-font-1; +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_dropdowns.scss b/openecomp-ui/resources/scss/bootstrap-cust/_dropdowns.scss new file mode 100644 index 0000000000..d6f895261c --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_dropdowns.scss @@ -0,0 +1,16 @@ + +.caret { + right: 8px; + top: 12px; + position: absolute; +} + +.dropdown-menu { + padding: 0; + li a { + height: 30px; + } + .divider { + margin: 0; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_forms.scss b/openecomp-ui/resources/scss/bootstrap-cust/_forms.scss new file mode 100644 index 0000000000..545b23ee7f --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_forms.scss @@ -0,0 +1,110 @@ +.form-group { + .control-label { + @extend .body-2-medium; + } + &.required { + label:before { + content: "*"; + color: $red; + margin: 0 4px 0 0; + } + } +} +.form-control { + @extend .body-1; + border-radius: 2px; + height: 30px; + @include box-shadow(none); + &:focus { + @include box-shadow(none); + } + &:hover { + border-color: $gray; + } +} + +label { + @extend .body-3; + margin-bottom: 8px; +} + +select.form-control { + display: block; + width: 215px; +} + +select[multiple] { + background: none; +} + +input[type="radio"], input[type="checkbox"] { + margin: 0; + + &:before { + content: ""; + display: inline-block; + width: 11px; + height: 11px; + margin-right: 10px; + position: absolute; + background-color: $white; + border: 1px solid $blue; + box-sizing: content-box; + } +} + +.radio label { + font-weight: normal; + display: inline-block; + cursor: pointer; + margin-right: $body-font-1; + font-size: $body-font-1; +} + +.checkbox label { + font-weight: normal; + display: inline-block; + cursor: pointer; + margin-right: $body-font-1; + font-size: $body-font-1; +} + +.radio input:before { + border-radius: 8px; +} + +.checkbox input:before { + border-radius: 2px; +} + +input[type=radio]:checked:before { + content: "\2022"; + color: $blue; + font-size: 30px; + text-align: center; + line-height: 11px; + font-family: $radio-font-family; +} + +input[type=checkbox]:checked:before { + font-family: $icon-font-family; + content: "\f00c"; + font-size: $icon-font-size; + color: $blue; + text-align: center; +} + +.radio, +.checkbox { + margin-top: 0px; + margin-bottom: 0px; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + top: 2px; +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_list-group.scss b/openecomp-ui/resources/scss/bootstrap-cust/_list-group.scss new file mode 100644 index 0000000000..b94f91bac9 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_list-group.scss @@ -0,0 +1,5 @@ + +.list-group-item { + border: none; + border-top: 3px solid $list-group-border; +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_mixins.scss b/openecomp-ui/resources/scss/bootstrap-cust/_mixins.scss new file mode 100644 index 0000000000..774e662014 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_mixins.scss @@ -0,0 +1,3 @@ + + +@import "mixins/buttons";
\ No newline at end of file diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_modals.scss b/openecomp-ui/resources/scss/bootstrap-cust/_modals.scss new file mode 100644 index 0000000000..6bc6e46b2f --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_modals.scss @@ -0,0 +1,18 @@ +.modal-content { + .modal-header { + border-top: 3px solid $blue; + .modal-title { + @extend .heading-2; + } + } + + .modal-body{ + padding: 15px; + } + + .modal-footer { + padding: 15px; + border-top: 0; + background-color: $tlv-gray; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_navbar.scss b/openecomp-ui/resources/scss/bootstrap-cust/_navbar.scss new file mode 100644 index 0000000000..acf2024b56 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_navbar.scss @@ -0,0 +1,3 @@ +.navbar { + border: none; +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_navs.scss b/openecomp-ui/resources/scss/bootstrap-cust/_navs.scss new file mode 100644 index 0000000000..7b9cff963d --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_navs.scss @@ -0,0 +1,30 @@ +.nav { + > li { + > a { + padding: 10px 10px 3px; + } + } +} + +.nav-tabs { + @include box-shadow(0px 2px 1px -1px $gray); + padding: 0 28px; + + > li { + @extend .body-1; + + > a { + color: $dark-gray; + text-transform: uppercase; + } + + &.active > a { + &, + &:hover, + &:focus { + @extend .body-1-medium; + border-bottom: 3px solid $blue; + } + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_panels.scss b/openecomp-ui/resources/scss/bootstrap-cust/_panels.scss new file mode 100644 index 0000000000..9ee5622292 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_panels.scss @@ -0,0 +1,3 @@ +.panel { + border: none; +}
\ No newline at end of file diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_tables.scss b/openecomp-ui/resources/scss/bootstrap-cust/_tables.scss new file mode 100644 index 0000000000..6902914f35 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_tables.scss @@ -0,0 +1,42 @@ +.table { + // Cells + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + border-top: none; + } + } + } + // Bottom align for column headings + > thead > tr > th { + border-bottom: none; + } + // Account for multiple tbody instances + > tbody + tbody { + border-top: none; + } + + +} + +.table-striped { + > tbody > tr:nth-of-type( odd ) { + background-color: $background-gray; + } +} + +.table-striped { + > tbody > tr:nth-of-type( even ) { + background-color: $gray; + } +} + +.table-hover { + > tbody > tr:hover { + background-color: $tlv-hover; + cursor: pointer; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap-cust/_variables.scss b/openecomp-ui/resources/scss/bootstrap-cust/_variables.scss new file mode 100644 index 0000000000..59f0e3db7c --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap-cust/_variables.scss @@ -0,0 +1,127 @@ + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +$gray-darker: $dark-gray; +$gray-dark: $dark-gray; +$gray: $gray; +$gray-light: $light-gray; +$gray-lighter: $light-gray; + +$brand-primary: $blue; +$brand-success: $green; +$brand-info: $light-gray; +$brand-warning: $yellow; +$brand-danger: $red; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +$body-bg: $white; +$text-color: $text-black; +$link-color: $link-blue; +$link-hover-color: $blue; + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. +$font-family-sans-serif: omnes-regular, "Omnes-Regular", "Helvetica Neue", Helvetica, Arial, sans-serif; +//$font-family-base: $font-family-sans-serif !default; +$font-size-base: $body-font-2; +$font-size-large: $body-font-1; +$font-size-small: $body-font-3; +$font-size-h1: $heading-font-1; +$font-size-h2: $heading-font-2; +$font-size-h3: $heading-font-4; +$font-size-h4: $heading-font-5; + + + +//== Components +// +//## Define common padding and border radius sizes and more. +$border-radius-base: 0; +$border-radius-large: 0; +$border-radius-small: 0; + + +//== Buttons +// +//## For each of Bootstrap's buttons, define text, background and border color. + +// $btn-font-weight: normal !default; +$btn-default-color: $text-color; +$btn-default-bg: $white; +$btn-default-border: $light-gray; + +$btn-success-color: $white; + +// Allows for customizing button radius independently from global border radius +$btn-border-radius-base: 2px; +$btn-border-radius-large: 2px; +$btn-border-radius-small: 2px; + +//== Dropdowns +// +$dropdown-bg: $white; +$dropdown-border: $link-blue; +$dropdown-link-color: $text-black; +$dropdown-divider-bg: $gray; +//** Hover color for dropdown links. +$dropdown-link-hover-color: $black; +//** Hover background for dropdown links. +$dropdown-link-hover-bg: $tlv-hover; + +//** Active dropdown menu item text color. +$dropdown-link-active-color: $black; +//** Active dropdown menu item background color. +$dropdown-link-active-bg: $tlv-hover; + +//== Forms +// +//## +$form-group-margin-bottom: 24px; + + +$input-bg: $white; +$input-bg-disabled: $tlv-light-gray; +$input-color: $dark-gray; +$input-border: $light-gray; +$input-border-focus: $dark-blue; + +//== Modals +// +//## +$modal-content-bg: $white; +$modal-inner-padding: 0 15px; +$modal-title-padding: 30px 25px 10px 25px; + + +//== Close +// +//## +$close-color: $tlv-light-gray; + +//== Navs +// +//## +//=== Shared nav styles +$nav-link-hover-bg: transparent; + +$navbar-inverse-bg: $gray; + +//== Tabs +$nav-tabs-border-color: transparent; +$nav-tabs-link-hover-border-color: transparent; +$nav-tabs-active-link-hover-bg: transparent; +$nav-tabs-active-link-hover-color: $text-black; +$nav-tabs-active-link-hover-border-color: transparent; + + +//== Popovers +// +//## +$popover-bg: $background-gray; diff --git a/openecomp-ui/resources/scss/bootstrap.scss b/openecomp-ui/resources/scss/bootstrap.scss new file mode 100644 index 0000000000..a8c470216d --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap.scss @@ -0,0 +1,61 @@ +// DOX CORE +@import "common/variables"; +@import "common/typography"; + +// Core variables and mixins +@import "bootstrap-cust/variables"; +@import "bootstrap/variables"; +@import "bootstrap/mixins"; +// Reset and dependencies +@import "bootstrap/normalize"; +//@import "bootstrap/print"; +//@import "bootstrap/glyphicons"; + +// Core CSS +@import "bootstrap/scaffolding"; +@import "bootstrap/type"; +@import "bootstrap/code"; +@import "bootstrap/grid"; +@import "bootstrap/tables"; +@import "bootstrap-cust/tables"; +@import "bootstrap/forms"; +@import "bootstrap-cust/forms"; +@import "bootstrap/buttons"; +@import "bootstrap-cust/buttons"; +// Components +@import "bootstrap/component-animations"; +@import "bootstrap/dropdowns"; +@import "bootstrap-cust/dropdowns"; +@import "bootstrap/button-groups"; +@import "bootstrap/input-groups"; +@import "bootstrap/navs"; +@import "bootstrap-cust/navs"; +@import "bootstrap/navbar"; +@import "bootstrap-cust/navbar"; +@import "bootstrap/breadcrumbs"; +@import "bootstrap/pagination"; +@import "bootstrap/pager"; +@import "bootstrap/labels"; +@import "bootstrap/badges"; +@import "bootstrap/jumbotron"; +// @import "bootstrap/thumbnails"; +@import "bootstrap/alerts"; +@import "bootstrap/progress-bars"; +@import "bootstrap/media"; +@import "bootstrap/list-group"; +@import "bootstrap-cust/list-group"; +@import "bootstrap/panels"; +@import "bootstrap-cust/panels"; +// @import "bootstrap/responsive-embed"; +//@import "bootstrap/wells"; +@import "bootstrap/close"; +@import "bootstrap-cust/close"; +// Components w/ JavaScript +@import "bootstrap/modals"; +@import "bootstrap-cust/modals"; +@import "bootstrap/tooltip"; +@import "bootstrap/popovers"; +// @import "bootstrap/carousel"; +// Utility classes +@import "bootstrap/utilities"; +@import "bootstrap/responsive-utilities"; diff --git a/openecomp-ui/resources/scss/bootstrap/_alerts.scss b/openecomp-ui/resources/scss/bootstrap/_alerts.scss new file mode 100644 index 0000000000..206ec3226a --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_alerts.scss @@ -0,0 +1,72 @@ +// +// Alerts +// -------------------------------------------------- + +// Base styles +// ------------------------- + +.alert { + padding: $alert-padding; + margin-bottom: $line-height-computed; + border: 1px solid transparent; + border-radius: $alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing $headings-color + color: inherit; + } + + // Provide class for links that match alerts + .alert-link { + font-weight: $alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + + > p + p { + margin-top: 5px; + } +} + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. +.alert-dismissible { + padding-right: ($alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); +} + +.alert-info { + @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); +} + +.alert-warning { + @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); +} + +.alert-danger { + @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_badges.scss b/openecomp-ui/resources/scss/bootstrap/_badges.scss new file mode 100644 index 0000000000..ad595a9557 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_badges.scss @@ -0,0 +1,67 @@ +// +// Badges +// -------------------------------------------------- + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: $font-size-small; + font-weight: $badge-font-weight; + color: $badge-color; + line-height: $badge-line-height; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: $badge-bg; + border-radius: $badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .btn & { + position: relative; + top: -1px; + } + + .btn-xs &, + .btn-group-xs > .btn & { + top: 0; + padding: 1px 5px; + } + + // [converter] extracted a& to a.badge + + // Account for badges in navs + .list-group-item.active > &, + .nav-pills > .active > a > & { + color: $badge-active-color; + background-color: $badge-active-bg; + } + + .list-group-item > & { + float: right; + } + + .list-group-item > & + & { + margin-right: 5px; + } + + .nav-pills > li > a > & { + margin-left: 3px; + } +} + +// Hover state, but only for links +a.badge { + &:hover, + &:focus { + color: $badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_breadcrumbs.scss b/openecomp-ui/resources/scss/bootstrap/_breadcrumbs.scss new file mode 100644 index 0000000000..2041f0dc84 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_breadcrumbs.scss @@ -0,0 +1,25 @@ +// +// Breadcrumbs +// -------------------------------------------------- + +.breadcrumb { + padding: $breadcrumb-padding-vertical $breadcrumb-padding-horizontal; + margin-bottom: $line-height-computed; + list-style: none; + background-color: $breadcrumb-bg; + border-radius: $border-radius-base; + + > li { + display: inline-block; + + + li:before { + content: "#{$breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: $breadcrumb-color; + } + } + + > .active { + color: $breadcrumb-active-color; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_button-groups.scss b/openecomp-ui/resources/scss/bootstrap/_button-groups.scss new file mode 100644 index 0000000000..dc2906e44f --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_button-groups.scss @@ -0,0 +1,256 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-left: -5px; // Offset the first child's margin + @include clearfix; + + .btn, + .btn-group, + .input-group { + float: left; + } + > .btn, + > .btn-group, + > .input-group { + margin-left: 5px; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + @include border-right-radius(0); + } +} + +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + @include border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + @include border-right-radius(0); + } +} + +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + @include border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { + @extend .btn-xs; +} + +.btn-group-sm > .btn { + @extend .btn-sm; +} + +.btn-group-lg > .btn { + @extend .btn-lg; +} + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} + +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + @include box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + +// Reposition the caret +.btn .caret { + margin-left: 0; +} + +// Carets in other button sizes +.btn-lg .caret { + border-width: $caret-width-large $caret-width-large 0; + border-bottom-width: 0; +} + +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 $caret-width-large $caret-width-large; +} + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group, + > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + @include clearfix; + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + border-top-right-radius: $btn-border-radius-base; + @include border-bottom-radius(0); + } + &:last-child:not(:first-child) { + border-bottom-left-radius: $btn-border-radius-base; + @include border-top-radius(0); + } +} + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + @include border-bottom-radius(0); + } +} + +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + @include border-top-radius(0); +} + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .btn, + > .btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .btn-group .btn { + width: 100%; + } + + > .btn-group .dropdown-menu { + left: auto; + } +} + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .btn, + > .btn-group > .btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_buttons.scss b/openecomp-ui/resources/scss/bootstrap/_buttons.scss new file mode 100644 index 0000000000..a9c3f340d7 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_buttons.scss @@ -0,0 +1,170 @@ +// +// Buttons +// -------------------------------------------------- + +// Base styles +// -------------------------------------------------- + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: $btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $btn-border-radius-base); + @include user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + @include tab-focus; + } + } + + &:hover, + &:focus, + &.focus { + color: $btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + @include box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: $cursor-disabled; + @include opacity(.65); + @include box-shadow(none); + } + + // [converter] extracted a& to a.btn +} + +a.btn { + &.disabled, + fieldset[disabled] & { + pointer-events: none; // Future-proof disabling of clicks on `<a>` elements + } +} + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + @include button-variant($btn-default-color, $btn-default-bg, $btn-default-border); +} + +.btn-primary { + @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); +} + +// Success appears as green +.btn-success { + @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); +} + +// Info appears as blue-green +.btn-info { + @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); +} + +// Warning appears as orange +.btn-warning { + @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); +} + +// Danger and error appear as red +.btn-danger { + @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); +} + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: $link-color; + font-weight: normal; + border-radius: 0; + + &, + &:active, + &.active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + @include box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: $btn-link-disabled-color; + text-decoration: none; + } + } +} + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + @include button-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $btn-border-radius-large); +} + +.btn-sm { + // line-height: ensure proper height of button next to small input + @include button-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $btn-border-radius-small); +} + +.btn-xs { + @include button-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $line-height-small, $btn-border-radius-small); +} + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_carousel.scss b/openecomp-ui/resources/scss/bootstrap/_carousel.scss new file mode 100644 index 0000000000..5542ceb905 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_carousel.scss @@ -0,0 +1,266 @@ +// +// Carousel +// -------------------------------------------------- + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + @include transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + @include img-responsive; + line-height: 1; + } + + // WebKit CSS3 transforms for supported devices + @media all and (transform-3d), (-webkit-transform-3d) { + @include transition-transform(0.6s ease-in-out); + @include backface-visibility(hidden); + @include perspective(1000px); + + &.next, + &.active.right { + @include translate3d(100%, 0, 0); + left: 0; + } + &.prev, + &.active.left { + @include translate3d(-100%, 0, 0); + left: 0; + } + &.next.left, + &.prev.right, + &.active { + @include translate3d(0, 0, 0); + left: 0; + } + } + } + + > .active, + > .next, + > .prev { + display: block; + } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: $carousel-control-width; + @include opacity($carousel-control-opacity); + font-size: $carousel-control-font-size; + color: $carousel-control-color; + text-align: center; + text-shadow: $carousel-text-shadow; + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + @include gradient-horizontal($start-color: rgba(0, 0, 0, .5), $end-color: rgba(0, 0, 0, .0001)); + } + &.right { + left: auto; + right: 0; + @include gradient-horizontal($start-color: rgba(0, 0, 0, .0001), $end-color: rgba(0, 0, 0, .5)); + } + + // Hover/focus state + &:hover, + &:focus { + outline: 0; + color: $carousel-control-color; + text-decoration: none; + @include opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; + } + + .icon-prev { + &:before { + content: '\2039'; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a'; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid $carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0, 0, 0, 0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: $carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: $carousel-caption-color; + text-align: center; + text-shadow: $carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + +// Scale up controls for tablets and up +@media screen and (min-width: $screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicon-chevron-left, + .glyphicon-chevron-right, + .icon-prev, + .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .glyphicon-chevron-left, + .icon-prev { + margin-left: -15px; + } + .glyphicon-chevron-right, + .icon-next { + margin-right: -15px; + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_close.scss b/openecomp-ui/resources/scss/bootstrap/_close.scss new file mode 100644 index 0000000000..d3ea6ab0ba --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_close.scss @@ -0,0 +1,35 @@ +// +// Close icons +// -------------------------------------------------- + +.close { + float: right; + font-size: ($font-size-base * 1.5); + font-weight: $close-font-weight; + line-height: 1; + color: $close-color; + text-shadow: $close-text-shadow; + @include opacity(.2); + + &:hover, + &:focus { + color: $close-color; + text-decoration: none; + cursor: pointer; + @include opacity(.5); + } + + // [converter] extracted button& to button.close +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_code.scss b/openecomp-ui/resources/scss/bootstrap/_code.scss new file mode 100644 index 0000000000..ab74c19e74 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_code.scss @@ -0,0 +1,68 @@ +// +// Code (inline and block) +// -------------------------------------------------- + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: $font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: $code-color; + background-color: $code-bg; + border-radius: $border-radius-base; +} + +// User input typically entered via keyboard +kbd { + padding: 2px 4px; + font-size: 90%; + color: $kbd-color; + background-color: $kbd-bg; + border-radius: $border-radius-small; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + + kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; + } +} + +// Blocks of code +pre { + display: block; + padding: (($line-height-computed - 1) / 2); + margin: 0 0 ($line-height-computed / 2); + font-size: ($font-size-base - 1); // 14px to 13px + line-height: $line-height-base; + word-break: break-all; + word-wrap: break-word; + color: $pre-color; + background-color: $pre-bg; + border: 1px solid $pre-border-color; + border-radius: $border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: $pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_component-animations.scss b/openecomp-ui/resources/scss/bootstrap/_component-animations.scss new file mode 100644 index 0000000000..58befaa2ef --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_component-animations.scss @@ -0,0 +1,43 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. + +.fade { + opacity: 0; + @include transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + + &.in { + display: block; + } + // [converter] extracted tr&.in to tr.collapse.in + // [converter] extracted tbody&.in to tbody.collapse.in +} + +tr.collapse.in { + display: table-row; +} + +tbody.collapse.in { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + @include transition-property(height, visibility); + @include transition-duration(.35s); + @include transition-timing-function(ease); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_dropdowns.scss b/openecomp-ui/resources/scss/bootstrap/_dropdowns.scss new file mode 100644 index 0000000000..6fcfb9f48e --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_dropdowns.scss @@ -0,0 +1,217 @@ +// +// Dropdown menus +// -------------------------------------------------- + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: $caret-width-base dashed; + border-top: $caret-width-base solid \9; // IE8 + border-right: $caret-width-base solid transparent; + border-left: $caret-width-base solid transparent; +} + +// The dropdown wrapper (div) +.dropup, +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: $zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: $font-size-base; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + background-color: $dropdown-bg; + border: 1px solid $dropdown-fallback-border; // IE8 fallback + border: 1px solid $dropdown-border; + border-radius: $border-radius-base; + @include box-shadow(0 6px 12px rgba(0, 0, 0, .175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + // + // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + @include nav-divider($dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: $line-height-base; + color: $dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: $dropdown-link-hover-color; + background-color: $dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: $dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: $dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: $dropdown-link-disabled-color; + } + + // Nuke hover/focus effects + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + @include reset-filter; + cursor: $cursor-disabled; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + left: auto; // Reset the default from `.dropdown-menu` + right: 0; +} + +// With v3, we enabled auto-flipping if you have a dropdown within a right +// aligned nav component. To enable the undoing of that, we provide an override +// to restore the default dropdown menu alignment. +// +// This is only for left-aligning a dropdown menu within a `.navbar-right` or +// `.pull-right` nav component. +.dropdown-menu-left { + left: 0; + right: auto; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: $font-size-small; + line-height: $line-height-base; + color: $dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: ($zindex-dropdown - 10); +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: $caret-width-base dashed; + border-bottom: $caret-width-base solid \9; // IE8 + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; + } +} + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: $grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + right: 0; + left: auto; + } + // Necessary for overrides of the default right aligned menu. + // Will remove come v4 in all likelihood. + .dropdown-menu-left { + left: 0; + right: auto; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_forms.scss b/openecomp-ui/resources/scss/bootstrap/_forms.scss new file mode 100644 index 0000000000..042d4ec9bd --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_forms.scss @@ -0,0 +1,607 @@ +// +// Forms +// -------------------------------------------------- + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a `min-width: min-content;` on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: $line-height-computed; + font-size: ($font-size-base * 1.5); + line-height: inherit; + color: $legend-color; + border: 0; + border-bottom: 1px solid $legend-border-color; +} + +label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: bold; +} + +// Normalize form controls +// +// While most of our form styles require extra classes, some basic normalization +// is required to ensure optimum display with or without those classes to better +// address browser inconsistencies. + +// Override content-box in Normalize (* isn't specific enough) +input[type="search"] { + @include box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; +} + +input[type="file"] { + display: block; +} + +// Make range inputs behave like textual form controls +input[type="range"] { + display: block; + width: 100%; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + @include tab-focus; +} + +// Adjust output element +output { + display: block; + padding-top: ($padding-base-vertical + 1); + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; +} + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +// select +// textarea +// input[type="text"] +// input[type="password"] +// input[type="datetime"] +// input[type="datetime-local"] +// input[type="date"] +// input[type="month"] +// input[type="time"] +// input[type="week"] +// input[type="number"] +// input[type="email"] +// input[type="url"] +// input[type="search"] +// input[type="tel"] +// input[type="color"] + +.form-control { + display: block; + width: 100%; + height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: $padding-base-vertical $padding-base-horizontal; + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; + background-color: $input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid $input-border; + border-radius: $input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS. + @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); + @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); + + // Customize the `:focus` state to imitate native WebKit styles. + @include form-control-focus; + + // Placeholder + @include placeholder; + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &[disabled], + &[readonly], + fieldset[disabled] & { + background-color: $input-bg-disabled; + opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655 + } + + &[disabled], + fieldset[disabled] & { + cursor: $cursor-disabled; + } + + // [converter] extracted textarea& to textarea.form-control +} + +// Reset height for `textarea`s +textarea.form-control { + height: auto; +} + +// Search inputs in iOS +// +// This overrides the extra rounded corners on search inputs in iOS so that our +// `.form-control` class can properly style them. Note that this cannot simply +// be added to `.form-control` as it's not specific enough. For details, see +// https://github.com/twbs/bootstrap/issues/11586. + +input[type="search"] { + -webkit-appearance: none; +} + +// Special styles for iOS temporal inputs +// +// In Mobile Safari, setting `display: block` on temporal inputs causes the +// text within the input to become vertically misaligned. As a workaround, we +// set a pixel line-height that matches the given height of the input, but only +// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848 +// +// Note that as of 8.3, iOS doesn't support `datetime` or `week`. + +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"], + input[type="time"], + input[type="datetime-local"], + input[type="month"] { + &.form-control { + line-height: $input-height-base; + } + + &.input-sm, + .input-group-sm & { + line-height: $input-height-small; + } + + &.input-lg, + .input-group-lg & { + line-height: $input-height-large; + } + } +} + +// Form groups +// +// Designed to help with the organization and spacing of vertical forms. For +// horizontal forms, use the predefined grid classes. + +.form-group { + margin-bottom: $form-group-margin-bottom; +} + +// Checkboxes and radios +// +// Indent the labels to position radios/checkboxes as hanging controls. + +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; + + label { + min-height: $line-height-computed; // Ensure the input doesn't jump when there is no text + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; + } +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing +} + +// Radios and checkboxes on same line +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} + +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; // space out consecutive inline controls +} + +// Apply same disabled cursor tweak as for inputs +// Some special care is needed because <label>s don't inherit their parent's `cursor`. +// +// Note: Neither radios nor checkboxes can be readonly. +input[type="radio"], +input[type="checkbox"] { + &[disabled], + &.disabled, + fieldset[disabled] & { + cursor: $cursor-disabled; + } +} + +// These classes are used directly on <label>s +.radio-inline, +.checkbox-inline { + &.disabled, + fieldset[disabled] & { + cursor: $cursor-disabled; + } +} + +// These classes are used on elements with <label> descendants +.radio, +.checkbox { + &.disabled, + fieldset[disabled] & { + label { + cursor: $cursor-disabled; + } + } +} + +// Static form control text +// +// Apply class to a `p` element to make any string of text align with labels in +// a horizontal form layout. + +.form-control-static { + // Size it appropriately next to real form controls + padding-top: ($padding-base-vertical + 1); + padding-bottom: ($padding-base-vertical + 1); + // Remove default margin from `p` + margin-bottom: 0; + min-height: ($line-height-computed + $font-size-base); + + &.input-lg, + &.input-sm { + padding-left: 0; + padding-right: 0; + } +} + +// Form control sizing +// +// Build on `.form-control` with modifier classes to decrease or increase the +// height and font-size of form controls. +// +// The `.form-group-* form-control` variations are sadly duplicated to avoid the +// issue documented in https://github.com/twbs/bootstrap/issues/15074. + +@include input-size('.input-sm', $input-height-small, $padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $input-border-radius-small); +.form-group-sm { + .form-control { + height: $input-height-small; + padding: $padding-small-vertical $padding-small-horizontal; + font-size: $font-size-small; + line-height: $line-height-small; + border-radius: $input-border-radius-small; + } + select.form-control { + height: $input-height-small; + line-height: $input-height-small; + } + textarea.form-control, + select[multiple].form-control { + height: auto; + } + .form-control-static { + height: $input-height-small; + min-height: ($line-height-computed + $font-size-small); + padding: ($padding-small-vertical + 1) $padding-small-horizontal; + font-size: $font-size-small; + line-height: $line-height-small; + } +} + +@include input-size('.input-lg', $input-height-large, $padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $input-border-radius-large); +.form-group-lg { + .form-control { + height: $input-height-large; + padding: $padding-large-vertical $padding-large-horizontal; + font-size: $font-size-large; + line-height: $line-height-large; + border-radius: $input-border-radius-large; + } + select.form-control { + height: $input-height-large; + line-height: $input-height-large; + } + textarea.form-control, + select[multiple].form-control { + height: auto; + } + .form-control-static { + height: $input-height-large; + min-height: ($line-height-computed + $font-size-large); + padding: ($padding-large-vertical + 1) $padding-large-horizontal; + font-size: $font-size-large; + line-height: $line-height-large; + } +} + +// Form control feedback states +// +// Apply contextual and semantic states to individual form controls. + +.has-feedback { + // Enable absolute positioning + position: relative; + + // Ensure icons don't overlap text + .form-control { + padding-right: ($input-height-base * 1.25); + } +} + +// Feedback icon (requires .glyphicon classes) +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; // Ensure icon is above input groups + display: block; + width: $input-height-base; + height: $input-height-base; + line-height: $input-height-base; + text-align: center; + pointer-events: none; +} + +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: $input-height-large; + height: $input-height-large; + line-height: $input-height-large; +} + +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: $input-height-small; + height: $input-height-small; + line-height: $input-height-small; +} + +// Feedback states +.has-success { + @include form-control-validation($state-success-text, $state-success-text, $state-success-bg); +} + +.has-warning { + @include form-control-validation($state-warning-text, $state-warning-text, $state-warning-bg); +} + +.has-error { + @include form-control-validation($state-danger-text, $state-danger-text, $state-danger-bg); +} + +// Reposition feedback icon if input has visible label above +.has-feedback label { + + & ~ .form-control-feedback { + top: ($line-height-computed + 5); // Height of the `label` and its margin + } + &.sr-only ~ .form-control-feedback { + top: 0; + } +} + +// Help text +// +// Apply to any element you wish to create light text for placement immediately +// below a form control. Use for general help, formatting, or instructional text. + +.help-block { + display: block; // account for any element using help-block + margin-top: 5px; + margin-bottom: 10px; + color: lighten($text-color, 25%); // lighten the text some for contrast +} + +// Inline forms +// +// Make forms appear inline(-block) by adding the `.form-inline` class. Inline +// forms begin stacked on extra small (mobile) devices and then go inline when +// viewports reach <768px. +// +// Requires wrapping inputs and labels with `.form-group` for proper display of +// default HTML form controls and our custom form controls (e.g., input groups). +// +// Heads up! This is mixin-ed into `.navbar-form` in navbars.less. + +// [converter] extracted from `.form-inline` for libsass compatibility +@mixin form-inline { + + // Kick in the inline + @media (min-width: $screen-sm-min) { + // Inline-block all the things for "inline" + .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + // In navbar-form, allow folks to *not* use `.form-group` + .form-control { + display: inline-block; + width: auto; // Prevent labels from stacking above inputs in `.form-group` + vertical-align: middle; + } + + // Make static controls behave like regular ones + .form-control-static { + display: inline-block; + } + + .input-group { + display: inline-table; + vertical-align: middle; + + .input-group-addon, + .input-group-btn, + .form-control { + width: auto; + } + } + + // Input groups need that 100% width though + .input-group > .form-control { + width: 100%; + } + + .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + // Remove default margin on radios/checkboxes that were used for stacking, and + // then undo the floating of radios and checkboxes to match. + .radio, + .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + + label { + padding-left: 0; + } + } + .radio input[type="radio"], + .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + // Re-override the feedback icon. + .has-feedback .form-control-feedback { + top: 0; + } + } +} + +// [converter] extracted as `@mixin form-inline` for libsass compatibility +.form-inline { + @include form-inline; +} + +// Horizontal forms +// +// Horizontal forms are built on grid classes and allow you to create forms with +// labels on the left and inputs on the right. + +.form-horizontal { + + // Consistent vertical alignment of radios and checkboxes + // + // Labels also get some reset styles, but that is scoped to a media query below. + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: ($padding-base-vertical + 1); // Default padding plus a border + } + // Account for padding we're adding to ensure the alignment and of help text + // and other content below items + .radio, + .checkbox { + min-height: ($line-height-computed + ($padding-base-vertical + 1)); + } + + // Make form groups behave like rows + .form-group { + @include make-row; + } + + // Reset spacing and right align labels, but scope to media queries so that + // labels on narrow viewports stack the same as a default form example. + @media (min-width: $screen-sm-min) { + .control-label { + text-align: right; + margin-bottom: 0; + padding-top: ($padding-base-vertical + 1); // Default padding plus a border + } + } + + // Validation states + // + // Reposition the icon because it's now within a grid column and columns have + // `position: relative;` on them. Also accounts for the grid gutter padding. + .has-feedback .form-control-feedback { + right: floor(($grid-gutter-width / 2)); + } + + // Form group sizes + // + // Quick utility class for applying `.input-lg` and `.input-sm` styles to the + // inputs and labels within a `.form-group`. + .form-group-lg { + @media (min-width: $screen-sm-min) { + .control-label { + padding-top: (($padding-large-vertical * $line-height-large) + 1); + font-size: $font-size-large; + } + } + } + .form-group-sm { + @media (min-width: $screen-sm-min) { + .control-label { + padding-top: ($padding-small-vertical + 1); + font-size: $font-size-small; + } + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_glyphicons.scss b/openecomp-ui/resources/scss/bootstrap/_glyphicons.scss new file mode 100644 index 0000000000..581a109df0 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_glyphicons.scss @@ -0,0 +1,1616 @@ +// +// Glyphicons for Bootstrap +// +// Since icons are fonts, they can be placed anywhere text is placed and are +// thus automatically sized to match the surrounding child. To use, create an +// inline element with the appropriate classes, like so: +// +// <a href="#"><span class="glyphicon glyphicon-star"></span> Star</a> + +@at-root { + // Import the fonts + @font-face { + font-family: 'Glyphicons Halflings'; + src: url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.eot'), '#{$icon-font-path}#{$icon-font-name}.eot')); + src: url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.eot?#iefix'), '#{$icon-font-path}#{$icon-font-name}.eot?#iefix')) format('embedded-opentype'), + url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.woff2'), '#{$icon-font-path}#{$icon-font-name}.woff2')) format('woff2'), + url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.woff'), '#{$icon-font-path}#{$icon-font-name}.woff')) format('woff'), + url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.ttf'), '#{$icon-font-path}#{$icon-font-name}.ttf')) format('truetype'), + url(if($bootstrap-sass-asset-helper, twbs-font-path('#{$icon-font-path}#{$icon-font-name}.svg##{$icon-font-svg-id}'), '#{$icon-font-path}#{$icon-font-name}.svg##{$icon-font-svg-id}')) format('svg'); + } +} + +// Catchall baseclass +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// Individual icons +.glyphicon-asterisk { + &:before { + content: "\2a"; + } +} + +.glyphicon-plus { + &:before { + content: "\2b"; + } +} + +.glyphicon-euro, +.glyphicon-eur { + &:before { + content: "\20ac"; + } +} + +.glyphicon-minus { + &:before { + content: "\2212"; + } +} + +.glyphicon-cloud { + &:before { + content: "\2601"; + } +} + +.glyphicon-envelope { + &:before { + content: "\2709"; + } +} + +.glyphicon-pencil { + &:before { + content: "\270f"; + } +} + +.glyphicon-glass { + &:before { + content: "\e001"; + } +} + +.glyphicon-music { + &:before { + content: "\e002"; + } +} + +.glyphicon-search { + &:before { + content: "\e003"; + } +} + +.glyphicon-heart { + &:before { + content: "\e005"; + } +} + +.glyphicon-star { + &:before { + content: "\e006"; + } +} + +.glyphicon-star-empty { + &:before { + content: "\e007"; + } +} + +.glyphicon-user { + &:before { + content: "\e008"; + } +} + +.glyphicon-film { + &:before { + content: "\e009"; + } +} + +.glyphicon-th-large { + &:before { + content: "\e010"; + } +} + +.glyphicon-th { + &:before { + content: "\e011"; + } +} + +.glyphicon-th-list { + &:before { + content: "\e012"; + } +} + +.glyphicon-ok { + &:before { + content: "\e013"; + } +} + +.glyphicon-remove { + &:before { + content: "\e014"; + } +} + +.glyphicon-zoom-in { + &:before { + content: "\e015"; + } +} + +.glyphicon-zoom-out { + &:before { + content: "\e016"; + } +} + +.glyphicon-off { + &:before { + content: "\e017"; + } +} + +.glyphicon-signal { + &:before { + content: "\e018"; + } +} + +.glyphicon-cog { + &:before { + content: "\e019"; + } +} + +.glyphicon-trash { + &:before { + content: "\e020"; + } +} + +.glyphicon-home { + &:before { + content: "\e021"; + } +} + +.glyphicon-file { + &:before { + content: "\e022"; + } +} + +.glyphicon-time { + &:before { + content: "\e023"; + } +} + +.glyphicon-road { + &:before { + content: "\e024"; + } +} + +.glyphicon-download-alt { + &:before { + content: "\e025"; + } +} + +.glyphicon-download { + &:before { + content: "\e026"; + } +} + +.glyphicon-upload { + &:before { + content: "\e027"; + } +} + +.glyphicon-inbox { + &:before { + content: "\e028"; + } +} + +.glyphicon-play-circle { + &:before { + content: "\e029"; + } +} + +.glyphicon-repeat { + &:before { + content: "\e030"; + } +} + +.glyphicon-refresh { + &:before { + content: "\e031"; + } +} + +.glyphicon-list-alt { + &:before { + content: "\e032"; + } +} + +.glyphicon-lock { + &:before { + content: "\e033"; + } +} + +.glyphicon-flag { + &:before { + content: "\e034"; + } +} + +.glyphicon-headphones { + &:before { + content: "\e035"; + } +} + +.glyphicon-volume-off { + &:before { + content: "\e036"; + } +} + +.glyphicon-volume-down { + &:before { + content: "\e037"; + } +} + +.glyphicon-volume-up { + &:before { + content: "\e038"; + } +} + +.glyphicon-qrcode { + &:before { + content: "\e039"; + } +} + +.glyphicon-barcode { + &:before { + content: "\e040"; + } +} + +.glyphicon-tag { + &:before { + content: "\e041"; + } +} + +.glyphicon-tags { + &:before { + content: "\e042"; + } +} + +.glyphicon-book { + &:before { + content: "\e043"; + } +} + +.glyphicon-bookmark { + &:before { + content: "\e044"; + } +} + +.glyphicon-print { + &:before { + content: "\e045"; + } +} + +.glyphicon-camera { + &:before { + content: "\e046"; + } +} + +.glyphicon-font { + &:before { + content: "\e047"; + } +} + +.glyphicon-bold { + &:before { + content: "\e048"; + } +} + +.glyphicon-italic { + &:before { + content: "\e049"; + } +} + +.glyphicon-text-height { + &:before { + content: "\e050"; + } +} + +.glyphicon-text-width { + &:before { + content: "\e051"; + } +} + +.glyphicon-align-left { + &:before { + content: "\e052"; + } +} + +.glyphicon-align-center { + &:before { + content: "\e053"; + } +} + +.glyphicon-align-right { + &:before { + content: "\e054"; + } +} + +.glyphicon-align-justify { + &:before { + content: "\e055"; + } +} + +.glyphicon-list { + &:before { + content: "\e056"; + } +} + +.glyphicon-indent-left { + &:before { + content: "\e057"; + } +} + +.glyphicon-indent-right { + &:before { + content: "\e058"; + } +} + +.glyphicon-facetime-video { + &:before { + content: "\e059"; + } +} + +.glyphicon-picture { + &:before { + content: "\e060"; + } +} + +.glyphicon-map-marker { + &:before { + content: "\e062"; + } +} + +.glyphicon-adjust { + &:before { + content: "\e063"; + } +} + +.glyphicon-tint { + &:before { + content: "\e064"; + } +} + +.glyphicon-edit { + &:before { + content: "\e065"; + } +} + +.glyphicon-share { + &:before { + content: "\e066"; + } +} + +.glyphicon-check { + &:before { + content: "\e067"; + } +} + +.glyphicon-move { + &:before { + content: "\e068"; + } +} + +.glyphicon-step-backward { + &:before { + content: "\e069"; + } +} + +.glyphicon-fast-backward { + &:before { + content: "\e070"; + } +} + +.glyphicon-backward { + &:before { + content: "\e071"; + } +} + +.glyphicon-play { + &:before { + content: "\e072"; + } +} + +.glyphicon-pause { + &:before { + content: "\e073"; + } +} + +.glyphicon-stop { + &:before { + content: "\e074"; + } +} + +.glyphicon-forward { + &:before { + content: "\e075"; + } +} + +.glyphicon-fast-forward { + &:before { + content: "\e076"; + } +} + +.glyphicon-step-forward { + &:before { + content: "\e077"; + } +} + +.glyphicon-eject { + &:before { + content: "\e078"; + } +} + +.glyphicon-chevron-left { + &:before { + content: "\e079"; + } +} + +.glyphicon-chevron-right { + &:before { + content: "\e080"; + } +} + +.glyphicon-plus-sign { + &:before { + content: "\e081"; + } +} + +.glyphicon-minus-sign { + &:before { + content: "\e082"; + } +} + +.glyphicon-remove-sign { + &:before { + content: "\e083"; + } +} + +.glyphicon-ok-sign { + &:before { + content: "\e084"; + } +} + +.glyphicon-question-sign { + &:before { + content: "\e085"; + } +} + +.glyphicon-info-sign { + &:before { + content: "\e086"; + } +} + +.glyphicon-screenshot { + &:before { + content: "\e087"; + } +} + +.glyphicon-remove-circle { + &:before { + content: "\e088"; + } +} + +.glyphicon-ok-circle { + &:before { + content: "\e089"; + } +} + +.glyphicon-ban-circle { + &:before { + content: "\e090"; + } +} + +.glyphicon-arrow-left { + &:before { + content: "\e091"; + } +} + +.glyphicon-arrow-right { + &:before { + content: "\e092"; + } +} + +.glyphicon-arrow-up { + &:before { + content: "\e093"; + } +} + +.glyphicon-arrow-down { + &:before { + content: "\e094"; + } +} + +.glyphicon-share-alt { + &:before { + content: "\e095"; + } +} + +.glyphicon-resize-full { + &:before { + content: "\e096"; + } +} + +.glyphicon-resize-small { + &:before { + content: "\e097"; + } +} + +.glyphicon-exclamation-sign { + &:before { + content: "\e101"; + } +} + +.glyphicon-gift { + &:before { + content: "\e102"; + } +} + +.glyphicon-leaf { + &:before { + content: "\e103"; + } +} + +.glyphicon-fire { + &:before { + content: "\e104"; + } +} + +.glyphicon-eye-open { + &:before { + content: "\e105"; + } +} + +.glyphicon-eye-close { + &:before { + content: "\e106"; + } +} + +.glyphicon-warning-sign { + &:before { + content: "\e107"; + } +} + +.glyphicon-plane { + &:before { + content: "\e108"; + } +} + +.glyphicon-calendar { + &:before { + content: "\e109"; + } +} + +.glyphicon-random { + &:before { + content: "\e110"; + } +} + +.glyphicon-comment { + &:before { + content: "\e111"; + } +} + +.glyphicon-magnet { + &:before { + content: "\e112"; + } +} + +.glyphicon-chevron-up { + &:before { + content: "\e113"; + } +} + +.glyphicon-chevron-down { + &:before { + content: "\e114"; + } +} + +.glyphicon-retweet { + &:before { + content: "\e115"; + } +} + +.glyphicon-shopping-cart { + &:before { + content: "\e116"; + } +} + +.glyphicon-folder-close { + &:before { + content: "\e117"; + } +} + +.glyphicon-folder-open { + &:before { + content: "\e118"; + } +} + +.glyphicon-resize-vertical { + &:before { + content: "\e119"; + } +} + +.glyphicon-resize-horizontal { + &:before { + content: "\e120"; + } +} + +.glyphicon-hdd { + &:before { + content: "\e121"; + } +} + +.glyphicon-bullhorn { + &:before { + content: "\e122"; + } +} + +.glyphicon-bell { + &:before { + content: "\e123"; + } +} + +.glyphicon-certificate { + &:before { + content: "\e124"; + } +} + +.glyphicon-thumbs-up { + &:before { + content: "\e125"; + } +} + +.glyphicon-thumbs-down { + &:before { + content: "\e126"; + } +} + +.glyphicon-hand-right { + &:before { + content: "\e127"; + } +} + +.glyphicon-hand-left { + &:before { + content: "\e128"; + } +} + +.glyphicon-hand-up { + &:before { + content: "\e129"; + } +} + +.glyphicon-hand-down { + &:before { + content: "\e130"; + } +} + +.glyphicon-circle-arrow-right { + &:before { + content: "\e131"; + } +} + +.glyphicon-circle-arrow-left { + &:before { + content: "\e132"; + } +} + +.glyphicon-circle-arrow-up { + &:before { + content: "\e133"; + } +} + +.glyphicon-circle-arrow-down { + &:before { + content: "\e134"; + } +} + +.glyphicon-globe { + &:before { + content: "\e135"; + } +} + +.glyphicon-wrench { + &:before { + content: "\e136"; + } +} + +.glyphicon-tasks { + &:before { + content: "\e137"; + } +} + +.glyphicon-filter { + &:before { + content: "\e138"; + } +} + +.glyphicon-briefcase { + &:before { + content: "\e139"; + } +} + +.glyphicon-fullscreen { + &:before { + content: "\e140"; + } +} + +.glyphicon-dashboard { + &:before { + content: "\e141"; + } +} + +.glyphicon-paperclip { + &:before { + content: "\e142"; + } +} + +.glyphicon-heart-empty { + &:before { + content: "\e143"; + } +} + +.glyphicon-link { + &:before { + content: "\e144"; + } +} + +.glyphicon-phone { + &:before { + content: "\e145"; + } +} + +.glyphicon-pushpin { + &:before { + content: "\e146"; + } +} + +.glyphicon-usd { + &:before { + content: "\e148"; + } +} + +.glyphicon-gbp { + &:before { + content: "\e149"; + } +} + +.glyphicon-sort { + &:before { + content: "\e150"; + } +} + +.glyphicon-sort-by-alphabet { + &:before { + content: "\e151"; + } +} + +.glyphicon-sort-by-alphabet-alt { + &:before { + content: "\e152"; + } +} + +.glyphicon-sort-by-order { + &:before { + content: "\e153"; + } +} + +.glyphicon-sort-by-order-alt { + &:before { + content: "\e154"; + } +} + +.glyphicon-sort-by-attributes { + &:before { + content: "\e155"; + } +} + +.glyphicon-sort-by-attributes-alt { + &:before { + content: "\e156"; + } +} + +.glyphicon-unchecked { + &:before { + content: "\e157"; + } +} + +.glyphicon-expand { + &:before { + content: "\e158"; + } +} + +.glyphicon-collapse-down { + &:before { + content: "\e159"; + } +} + +.glyphicon-collapse-up { + &:before { + content: "\e160"; + } +} + +.glyphicon-log-in { + &:before { + content: "\e161"; + } +} + +.glyphicon-flash { + &:before { + content: "\e162"; + } +} + +.glyphicon-log-out { + &:before { + content: "\e163"; + } +} + +.glyphicon-new-window { + &:before { + content: "\e164"; + } +} + +.glyphicon-record { + &:before { + content: "\e165"; + } +} + +.glyphicon-save { + &:before { + content: "\e166"; + } +} + +.glyphicon-open { + &:before { + content: "\e167"; + } +} + +.glyphicon-saved { + &:before { + content: "\e168"; + } +} + +.glyphicon-import { + &:before { + content: "\e169"; + } +} + +.glyphicon-export { + &:before { + content: "\e170"; + } +} + +.glyphicon-send { + &:before { + content: "\e171"; + } +} + +.glyphicon-floppy-disk { + &:before { + content: "\e172"; + } +} + +.glyphicon-floppy-saved { + &:before { + content: "\e173"; + } +} + +.glyphicon-floppy-remove { + &:before { + content: "\e174"; + } +} + +.glyphicon-floppy-save { + &:before { + content: "\e175"; + } +} + +.glyphicon-floppy-open { + &:before { + content: "\e176"; + } +} + +.glyphicon-credit-card { + &:before { + content: "\e177"; + } +} + +.glyphicon-transfer { + &:before { + content: "\e178"; + } +} + +.glyphicon-cutlery { + &:before { + content: "\e179"; + } +} + +.glyphicon-header { + &:before { + content: "\e180"; + } +} + +.glyphicon-compressed { + &:before { + content: "\e181"; + } +} + +.glyphicon-earphone { + &:before { + content: "\e182"; + } +} + +.glyphicon-phone-alt { + &:before { + content: "\e183"; + } +} + +.glyphicon-tower { + &:before { + content: "\e184"; + } +} + +.glyphicon-stats { + &:before { + content: "\e185"; + } +} + +.glyphicon-sd-video { + &:before { + content: "\e186"; + } +} + +.glyphicon-hd-video { + &:before { + content: "\e187"; + } +} + +.glyphicon-subtitles { + &:before { + content: "\e188"; + } +} + +.glyphicon-sound-stereo { + &:before { + content: "\e189"; + } +} + +.glyphicon-sound-dolby { + &:before { + content: "\e190"; + } +} + +.glyphicon-sound-5-1 { + &:before { + content: "\e191"; + } +} + +.glyphicon-sound-6-1 { + &:before { + content: "\e192"; + } +} + +.glyphicon-sound-7-1 { + &:before { + content: "\e193"; + } +} + +.glyphicon-copyright-mark { + &:before { + content: "\e194"; + } +} + +.glyphicon-registration-mark { + &:before { + content: "\e195"; + } +} + +.glyphicon-cloud-download { + &:before { + content: "\e197"; + } +} + +.glyphicon-cloud-upload { + &:before { + content: "\e198"; + } +} + +.glyphicon-tree-conifer { + &:before { + content: "\e199"; + } +} + +.glyphicon-tree-deciduous { + &:before { + content: "\e200"; + } +} + +.glyphicon-cd { + &:before { + content: "\e201"; + } +} + +.glyphicon-save-file { + &:before { + content: "\e202"; + } +} + +.glyphicon-open-file { + &:before { + content: "\e203"; + } +} + +.glyphicon-level-up { + &:before { + content: "\e204"; + } +} + +.glyphicon-copy { + &:before { + content: "\e205"; + } +} + +.glyphicon-paste { + &:before { + content: "\e206"; + } +} + +// The following 2 Glyphicons are omitted for the time being because +// they currently use Unicode codepoints that are outside the +// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle +// non-BMP codepoints in CSS string escapes, and thus can't display these two icons. +// Notably, the bug affects some older versions of the Android Browser. +// More info: https://github.com/twbs/bootstrap/issues/10106 +// .glyphicon-door { &:before { content: "\1f6aa"; } } +// .glyphicon-key { &:before { content: "\1f511"; } } +.glyphicon-alert { + &:before { + content: "\e209"; + } +} + +.glyphicon-equalizer { + &:before { + content: "\e210"; + } +} + +.glyphicon-king { + &:before { + content: "\e211"; + } +} + +.glyphicon-queen { + &:before { + content: "\e212"; + } +} + +.glyphicon-pawn { + &:before { + content: "\e213"; + } +} + +.glyphicon-bishop { + &:before { + content: "\e214"; + } +} + +.glyphicon-knight { + &:before { + content: "\e215"; + } +} + +.glyphicon-baby-formula { + &:before { + content: "\e216"; + } +} + +.glyphicon-tent { + &:before { + content: "\26fa"; + } +} + +.glyphicon-blackboard { + &:before { + content: "\e218"; + } +} + +.glyphicon-bed { + &:before { + content: "\e219"; + } +} + +.glyphicon-apple { + &:before { + content: "\f8ff"; + } +} + +.glyphicon-erase { + &:before { + content: "\e221"; + } +} + +.glyphicon-hourglass { + &:before { + content: "\231b"; + } +} + +.glyphicon-lamp { + &:before { + content: "\e223"; + } +} + +.glyphicon-duplicate { + &:before { + content: "\e224"; + } +} + +.glyphicon-piggy-bank { + &:before { + content: "\e225"; + } +} + +.glyphicon-scissors { + &:before { + content: "\e226"; + } +} + +.glyphicon-bitcoin { + &:before { + content: "\e227"; + } +} + +.glyphicon-btc { + &:before { + content: "\e227"; + } +} + +.glyphicon-xbt { + &:before { + content: "\e227"; + } +} + +.glyphicon-yen { + &:before { + content: "\00a5"; + } +} + +.glyphicon-jpy { + &:before { + content: "\00a5"; + } +} + +.glyphicon-ruble { + &:before { + content: "\20bd"; + } +} + +.glyphicon-rub { + &:before { + content: "\20bd"; + } +} + +.glyphicon-scale { + &:before { + content: "\e230"; + } +} + +.glyphicon-ice-lolly { + &:before { + content: "\e231"; + } +} + +.glyphicon-ice-lolly-tasted { + &:before { + content: "\e232"; + } +} + +.glyphicon-education { + &:before { + content: "\e233"; + } +} + +.glyphicon-option-horizontal { + &:before { + content: "\e234"; + } +} + +.glyphicon-option-vertical { + &:before { + content: "\e235"; + } +} + +.glyphicon-menu-hamburger { + &:before { + content: "\e236"; + } +} + +.glyphicon-modal-window { + &:before { + content: "\e237"; + } +} + +.glyphicon-oil { + &:before { + content: "\e238"; + } +} + +.glyphicon-grain { + &:before { + content: "\e239"; + } +} + +.glyphicon-sunglasses { + &:before { + content: "\e240"; + } +} + +.glyphicon-text-size { + &:before { + content: "\e241"; + } +} + +.glyphicon-text-color { + &:before { + content: "\e242"; + } +} + +.glyphicon-text-background { + &:before { + content: "\e243"; + } +} + +.glyphicon-object-align-top { + &:before { + content: "\e244"; + } +} + +.glyphicon-object-align-bottom { + &:before { + content: "\e245"; + } +} + +.glyphicon-object-align-horizontal { + &:before { + content: "\e246"; + } +} + +.glyphicon-object-align-left { + &:before { + content: "\e247"; + } +} + +.glyphicon-object-align-vertical { + &:before { + content: "\e248"; + } +} + +.glyphicon-object-align-right { + &:before { + content: "\e249"; + } +} + +.glyphicon-triangle-right { + &:before { + content: "\e250"; + } +} + +.glyphicon-triangle-left { + &:before { + content: "\e251"; + } +} + +.glyphicon-triangle-bottom { + &:before { + content: "\e252"; + } +} + +.glyphicon-triangle-top { + &:before { + content: "\e253"; + } +} + +.glyphicon-console { + &:before { + content: "\e254"; + } +} + +.glyphicon-superscript { + &:before { + content: "\e255"; + } +} + +.glyphicon-subscript { + &:before { + content: "\e256"; + } +} + +.glyphicon-menu-left { + &:before { + content: "\e257"; + } +} + +.glyphicon-menu-right { + &:before { + content: "\e258"; + } +} + +.glyphicon-menu-down { + &:before { + content: "\e259"; + } +} + +.glyphicon-menu-up { + &:before { + content: "\e260"; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_grid.scss b/openecomp-ui/resources/scss/bootstrap/_grid.scss new file mode 100644 index 0000000000..75f3822540 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_grid.scss @@ -0,0 +1,76 @@ +// +// Grid system +// -------------------------------------------------- + +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +.container { + @include container-fixed; + + @media (min-width: $screen-sm-min) { + width: $container-sm; + } + @media (min-width: $screen-md-min) { + width: $container-md; + } + @media (min-width: $screen-lg-min) { + width: $container-lg; + } +} + +// Fluid container +// +// Utilizes the mixin meant for fixed width containers, but without any defined +// width for fluid, full width layouts. + +.container-fluid { + @include container-fixed; +} + +// Row +// +// Rows contain and clear the floats of your columns. + +.row { + @include make-row; +} + +// Columns +// +// Common styles for small and large grid columns + +@include make-grid-columns; + +// Extra small grid +// +// Columns, offsets, pushes, and pulls for extra small devices like +// smartphones. + +@include make-grid(xs); + +// Small grid +// +// Columns, offsets, pushes, and pulls for the small device range, from phones +// to tablets. + +@media (min-width: $screen-sm-min) { + @include make-grid(sm); +} + +// Medium grid +// +// Columns, offsets, pushes, and pulls for the desktop device range. + +@media (min-width: $screen-md-min) { + @include make-grid(md); +} + +// Large grid +// +// Columns, offsets, pushes, and pulls for the large desktop device range. + +@media (min-width: $screen-lg-min) { + @include make-grid(lg); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_input-groups.scss b/openecomp-ui/resources/scss/bootstrap/_input-groups.scss new file mode 100644 index 0000000000..4a160e0787 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_input-groups.scss @@ -0,0 +1,171 @@ +// +// Input groups +// -------------------------------------------------- + +// Base styles +// ------------------------- +.input-group { + position: relative; // For dropdowns + display: table; + border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table + + // Undo padding and float of grid classes + &[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; + } + + .form-control { + // Ensure that the input is always above the *appended* addon button for + // proper border colors. + position: relative; + z-index: 2; + + // IE9 fubars the placeholder attribute in text inputs and the arrows on + // select elements in input groups. To fix it, we float the input. Details: + // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 + float: left; + + width: 100%; + margin-bottom: 0; + } +} + +// Sizing options +// +// Remix the default form control sizing classes into new ones for easier +// manipulation. + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + @extend .input-lg; +} + +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + @extend .input-sm; +} + +// Display as table-cell +// ------------------------- +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; + + &:not(:first-child):not(:last-child) { + border-radius: 0; + } +} + +// Addon and addon wrapper for buttons +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; // Match the inputs +} + +// Text input groups +// ------------------------- +.input-group-addon { + padding: $padding-base-vertical $padding-base-horizontal; + font-size: $font-size-base; + font-weight: normal; + line-height: 1; + color: $input-color; + text-align: center; + background-color: $input-group-addon-bg; + border: 1px solid $input-group-addon-border-color; + border-radius: $border-radius-base; + + // Sizing + &.input-sm { + padding: $padding-small-vertical $padding-small-horizontal; + font-size: $font-size-small; + border-radius: $border-radius-small; + } + &.input-lg { + padding: $padding-large-vertical $padding-large-horizontal; + font-size: $font-size-large; + border-radius: $border-radius-large; + } + + // Nuke default margins from checkboxes and radios to vertically center within. + input[type="radio"], + input[type="checkbox"] { + margin-top: 0; + } +} + +// Reset rounded corners +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + @include border-right-radius(0); +} + +.input-group-addon:first-child { + border-right: 0; +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + @include border-left-radius(0); +} + +.input-group-addon:last-child { + border-left: 0; +} + +// Button input groups +// ------------------------- +.input-group-btn { + position: relative; + // Jankily prevent input button groups from wrapping with `white-space` and + // `font-size` in combination with `inline-block` on buttons. + font-size: 0; + white-space: nowrap; + + // Negative margin for spacing, position for bringing hovered/focused/actived + // element above the siblings. + > .btn { + position: relative; + + .btn { + margin-left: -1px; + } + // Bring the "active" button to the front + &:hover, + &:focus, + &:active { + z-index: 2; + } + } + + // Negative margin to only have a 1px border between the two + &:first-child { + > .btn, + > .btn-group { + margin-right: -1px; + } + } + &:last-child { + > .btn, + > .btn-group { + z-index: 2; + margin-left: -1px; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_jumbotron.scss b/openecomp-ui/resources/scss/bootstrap/_jumbotron.scss new file mode 100644 index 0000000000..e31137137d --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_jumbotron.scss @@ -0,0 +1,51 @@ +// +// Jumbotron +// -------------------------------------------------- + +.jumbotron { + padding-top: $jumbotron-padding; + padding-bottom: $jumbotron-padding; + margin-bottom: $jumbotron-padding; + color: $jumbotron-color; + background-color: $jumbotron-bg; + + h1, + .h1 { + color: $jumbotron-heading-color; + } + + p { + margin-bottom: ($jumbotron-padding / 2); + font-size: $jumbotron-font-size; + font-weight: 200; + } + + > hr { + border-top-color: darken($jumbotron-bg, 10%); + } + + .container &, + .container-fluid & { + border-radius: $border-radius-large; // Only round corners at higher resolutions if contained in a container + } + + .container { + max-width: 100%; + } + + @media screen and (min-width: $screen-sm-min) { + padding-top: ($jumbotron-padding * 1.6); + padding-bottom: ($jumbotron-padding * 1.6); + + .container &, + .container-fluid & { + padding-left: ($jumbotron-padding * 2); + padding-right: ($jumbotron-padding * 2); + } + + h1, + .h1 { + font-size: $jumbotron-heading-font-size; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_labels.scss b/openecomp-ui/resources/scss/bootstrap/_labels.scss new file mode 100644 index 0000000000..4fea19d99a --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_labels.scss @@ -0,0 +1,66 @@ +// +// Labels +// -------------------------------------------------- + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: $label-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + + // [converter] extracted a& to a.label + + // Empty labels collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for labels in buttons + .btn & { + position: relative; + top: -1px; + } +} + +// Add hover effects, but only for links +a.label { + &:hover, + &:focus { + color: $label-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} + +// Colors +// Contextual variations (linked labels get darker on :hover) + +.label-default { + @include label-variant($label-default-bg); +} + +.label-primary { + @include label-variant($label-primary-bg); +} + +.label-success { + @include label-variant($label-success-bg); +} + +.label-info { + @include label-variant($label-info-bg); +} + +.label-warning { + @include label-variant($label-warning-bg); +} + +.label-danger { + @include label-variant($label-danger-bg); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_list-group.scss b/openecomp-ui/resources/scss/bootstrap/_list-group.scss new file mode 100644 index 0000000000..d04b72ee45 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_list-group.scss @@ -0,0 +1,126 @@ +// +// List groups +// -------------------------------------------------- + +// Base class +// +// Easily usable on <ul>, <ol>, or <div>. + +.list-group { + // No need to set list-style: none; since .list-group-item is block level + margin-bottom: 20px; + padding-left: 0; // reset padding because ul and ol +} + +// Individual list items +// +// Use on `li`s or `div`s within the `.list-group` parent. + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + // Place the border on the list items and negative margin up for better styling + margin-bottom: -1px; + background-color: $list-group-bg; + border: 1px solid $list-group-border; + + // Round the first and last items + &:first-child { + @include border-top-radius($list-group-border-radius); + } + &:last-child { + margin-bottom: 0; + @include border-bottom-radius($list-group-border-radius); + } +} + +// Interactive list items +// +// Use anchor or button elements instead of `li`s or `div`s to create interactive items. +// Includes an extra `.active` modifier class for showing selected items. + +a.list-group-item, +button.list-group-item { + color: $list-group-link-color; + + .list-group-item-heading { + color: $list-group-link-heading-color; + } + + // Hover state + &:hover, + &:focus { + text-decoration: none; + color: $list-group-link-hover-color; + background-color: $list-group-hover-bg; + } +} + +button.list-group-item { + width: 100%; + text-align: left; +} + +.list-group-item { + // Disabled state + &.disabled, + &.disabled:hover, + &.disabled:focus { + background-color: $list-group-disabled-bg; + color: $list-group-disabled-color; + cursor: $cursor-disabled; + + // Force color to inherit for custom content + .list-group-item-heading { + color: inherit; + } + .list-group-item-text { + color: $list-group-disabled-text-color; + } + } + + // Active class on item itself, not parent + &.active, + &.active:hover, + &.active:focus { + z-index: 2; // Place active items above their siblings for proper border styling + color: $list-group-active-color; + background-color: $list-group-active-bg; + border-color: $list-group-active-border; + + // Force color to inherit for custom content + .list-group-item-heading, + .list-group-item-heading > small, + .list-group-item-heading > .small { + color: inherit; + } + .list-group-item-text { + color: $list-group-active-text-color; + } + } +} + +// Contextual variants +// +// Add modifier classes to change text and background color on individual items. +// Organizationally, this must come after the `:hover` states. + +@include list-group-item-variant(success, $state-success-bg, $state-success-text); +@include list-group-item-variant(info, $state-info-bg, $state-info-text); +@include list-group-item-variant(warning, $state-warning-bg, $state-warning-text); +@include list-group-item-variant(danger, $state-danger-bg, $state-danger-text); + +// Custom content options +// +// Extra classes for creating well-formatted content within `.list-group-item`s. + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_media.scss b/openecomp-ui/resources/scss/bootstrap/_media.scss new file mode 100644 index 0000000000..b9a5663ee8 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_media.scss @@ -0,0 +1,66 @@ +.media { + // Proper spacing between instances of .media + margin-top: 15px; + + &:first-child { + margin-top: 0; + } +} + +.media, +.media-body { + zoom: 1; + overflow: hidden; +} + +.media-body { + width: 10000px; +} + +.media-object { + display: block; + + // Fix collapse in webkit from max-width: 100% and display: table-cell. + &.img-thumbnail { + max-width: none; + } +} + +.media-right, +.media > .pull-right { + padding-left: 10px; +} + +.media-left, +.media > .pull-left { + padding-right: 10px; +} + +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} + +.media-middle { + vertical-align: middle; +} + +.media-bottom { + vertical-align: bottom; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} + +// Media list variation +// +// Undo default ul/ol styles +.media-list { + padding-left: 0; + list-style: none; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_mixins.scss b/openecomp-ui/resources/scss/bootstrap/_mixins.scss new file mode 100644 index 0000000000..62dfda69dc --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_mixins.scss @@ -0,0 +1,36 @@ +// Mixins +// -------------------------------------------------- +// Utilities +@import "mixins/hide-text"; +@import "mixins/opacity"; +@import "mixins/image"; +@import "mixins/labels"; +@import "mixins/reset-filter"; +@import "mixins/resize"; +@import "mixins/responsive-visibility"; +@import "mixins/size"; +@import "mixins/tab-focus"; +@import "mixins/reset-text"; +@import "mixins/text-emphasis"; +@import "mixins/text-overflow"; +@import "mixins/vendor-prefixes"; +// Components +@import "mixins/alerts"; +@import "mixins/buttons"; +@import "mixins/panels"; +@import "mixins/pagination"; +@import "mixins/list-group"; +@import "mixins/nav-divider"; +@import "mixins/forms"; +@import "mixins/progress-bar"; +@import "mixins/table-row"; +// Skins +@import "mixins/background-variant"; +@import "mixins/border-radius"; +@import "mixins/gradients"; +// Layout +@import "mixins/clearfix"; +@import "mixins/center-block"; +@import "mixins/nav-vertical-align"; +@import "mixins/grid-framework"; +@import "mixins/grid"; diff --git a/openecomp-ui/resources/scss/bootstrap/_modals.scss b/openecomp-ui/resources/scss/bootstrap/_modals.scss new file mode 100644 index 0000000000..e089e7bfae --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_modals.scss @@ -0,0 +1,162 @@ +// +// Modals +// -------------------------------------------------- + +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and shit + +// Kill the scroll on the body +.modal-open { + overflow: hidden; +} + +// Container that the modal scrolls within +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal; + -webkit-overflow-scrolling: touch; + + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + + // When fading in the modal, animate it to slide down + &.fade .modal-dialog { + @include translate(0, -25%); + @include transition-transform(0.3s ease-out); + } + &.in .modal-dialog { + @include translate(0, 0) + } +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +// Actual modal +.modal-content { + position: relative; + background-color: $modal-content-bg; + border: 1px solid $modal-content-fallback-border-color; //old browsers fallback (ie8 etc) + border: 1px solid $modal-content-border-color; + border-radius: $border-radius-large; + @include box-shadow(0 3px 9px rgba(0, 0, 0, .5)); + background-clip: padding-box; + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal-background; + background-color: $modal-backdrop-bg; + // Fade for backdrop + &.fade { + @include opacity(0); + } + &.in { + @include opacity($modal-backdrop-opacity); + } +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + padding: $modal-title-padding; + border-bottom: 1px solid $modal-header-border-color; + min-height: ($modal-title-padding + $modal-title-line-height); +} + +// Close icon +.modal-header .close { + margin-top: -2px; +} + +// Title text within header +.modal-title { + margin: 0; + line-height: $modal-title-line-height; +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + padding: $modal-inner-padding; +} + +// Footer (for actions) +.modal-footer { + padding: $modal-inner-padding; + text-align: right; // right align buttons + border-top: 1px solid $modal-footer-border-color; + @include clearfix; // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +// Scale up the modal +@media (min-width: $screen-sm-min) { + // Automatically set modal's width for larger viewports + .modal-dialog { + width: $modal-md; + margin: 30px auto; + } + .modal-content { + @include box-shadow(0 5px 15px rgba(0, 0, 0, .5)); + } + + // Modal sizes + .modal-sm { + width: $modal-sm; + } +} + +@media (min-width: $screen-md-min) { + .modal-lg { + width: $modal-lg; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_navbar.scss b/openecomp-ui/resources/scss/bootstrap/_navbar.scss new file mode 100644 index 0000000000..af38fd9e97 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_navbar.scss @@ -0,0 +1,650 @@ +// +// Navbars +// -------------------------------------------------- + +// Wrapper and base class +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + position: relative; + min-height: $navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode) + margin-bottom: $navbar-margin-bottom; + border: 1px solid transparent; + + // Prevent floats from breaking the navbar + @include clearfix; + + @media (min-width: $grid-float-breakpoint) { + border-radius: $navbar-border-radius; + } +} + +// Navbar heading +// +// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy +// styling of responsive aspects. + +.navbar-header { + @include clearfix; + + @media (min-width: $grid-float-breakpoint) { + float: left; + } +} + +// Navbar collapse (body) +// +// Group your navbar content into this for easy collapsing and expanding across +// various device sizes. By default, this content is collapsed when <768px, but +// will expand past that for a horizontal display. +// +// To start (on mobile devices) the navbar links, forms, and buttons are stacked +// vertically and include a `max-height` to overflow in case you have too much +// content for the user's viewport. + +.navbar-collapse { + overflow-x: visible; + padding-right: $navbar-padding-horizontal; + padding-left: $navbar-padding-horizontal; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + @include clearfix; + -webkit-overflow-scrolling: touch; + + &.in { + overflow-y: auto; + } + + @media (min-width: $grid-float-breakpoint) { + width: auto; + border-top: 0; + box-shadow: none; + + &.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; // Override default setting + overflow: visible !important; + } + + &.in { + overflow-y: visible; + } + + // Undo the collapse side padding for navbars with containers to ensure + // alignment of right-aligned contents. + .navbar-fixed-top &, + .navbar-static-top &, + .navbar-fixed-bottom & { + padding-left: 0; + padding-right: 0; + } + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + .navbar-collapse { + max-height: $navbar-collapse-max-height; + + @media (max-device-width: $screen-xs-min) and (orientation: landscape) { + max-height: 200px; + } + } +} + +// Both navbar header and collapse +// +// When a container is present, change the behavior of the header and collapse. + +.container, +.container-fluid { + > .navbar-header, + > .navbar-collapse { + margin-right: -$navbar-padding-horizontal; + margin-left: -$navbar-padding-horizontal; + + @media (min-width: $grid-float-breakpoint) { + margin-right: 0; + margin-left: 0; + } + } +} + +// +// Navbar alignment options +// +// Display the navbar across the entirety of the page or fixed it to the top or +// bottom of the page. + +// Static top (unfixed, but 100% wide) navbar +.navbar-static-top { + z-index: $zindex-navbar; + border-width: 0 0 1px; + + @media (min-width: $grid-float-breakpoint) { + border-radius: 0; + } +} + +// Fix the top/bottom navbars when screen real estate supports it +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: $zindex-navbar-fixed; + + // Undo the rounded corners + @media (min-width: $grid-float-breakpoint) { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; // override .navbar defaults + border-width: 1px 0 0; +} + +// Brand/project name + +.navbar-brand { + float: left; + padding: $navbar-padding-vertical $navbar-padding-horizontal; + font-size: $font-size-large; + line-height: $line-height-computed; + height: $navbar-height; + + &:hover, + &:focus { + text-decoration: none; + } + + > img { + display: block; + } + + @media (min-width: $grid-float-breakpoint) { + .navbar > .container &, + .navbar > .container-fluid & { + margin-left: -$navbar-padding-horizontal; + } + } +} + +// Navbar toggle +// +// Custom button for toggling the `.navbar-collapse`, powered by the collapse +// JavaScript plugin. + +.navbar-toggle { + position: relative; + float: right; + margin-right: $navbar-padding-horizontal; + padding: 9px 10px; + @include navbar-vertical-align(34px); + background-color: transparent; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + border-radius: $border-radius-base; + + // We remove the `outline` here, but later compensate by attaching `:hover` + // styles to `:focus`. + &:focus { + outline: 0; + } + + // Bars + .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; + } + .icon-bar + .icon-bar { + margin-top: 4px; + } + + @media (min-width: $grid-float-breakpoint) { + display: none; + } +} + +// Navbar nav links +// +// Builds on top of the `.nav` components with its own modifier class to make +// the nav the full height of the horizontal nav (above 768px). + +.navbar-nav { + margin: ($navbar-padding-vertical / 2) (-$navbar-padding-horizontal); + + > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: $line-height-computed; + } + + @media (max-width: $grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + > li > a, + .dropdown-header { + padding: 5px 15px 5px 25px; + } + > li > a { + line-height: $line-height-computed; + &:hover, + &:focus { + background-image: none; + } + } + } + } + + // Uncollapse the nav + @media (min-width: $grid-float-breakpoint) { + float: left; + margin: 0; + + > li { + float: left; + > a { + padding-top: $navbar-padding-vertical; + padding-bottom: $navbar-padding-vertical; + } + } + } +} + +// Navbar form +// +// Extension of the `.form-inline` with some extra flavor for optimum display in +// our navbars. + +.navbar-form { + margin-left: -$navbar-padding-horizontal; + margin-right: -$navbar-padding-horizontal; + padding: 10px $navbar-padding-horizontal; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + $shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + @include box-shadow($shadow); + + // Mixin behavior for optimum display + @include form-inline; + + .form-group { + @media (max-width: $grid-float-breakpoint-max) { + margin-bottom: 5px; + + &:last-child { + margin-bottom: 0; + } + } + } + + // Vertically center in expanded, horizontal navbar + @include navbar-vertical-align($input-height-base); + + // Undo 100% width for pull classes + @media (min-width: $grid-float-breakpoint) { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + @include box-shadow(none); + } +} + +// Dropdown menus + +// Menu position and menu carets +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + @include border-top-radius(0); +} + +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + @include border-top-radius($navbar-border-radius); + @include border-bottom-radius(0); +} + +// Buttons in navbars +// +// Vertically center a button within a navbar (when *not* in a form). + +.navbar-btn { + @include navbar-vertical-align($input-height-base); + + &.btn-sm { + @include navbar-vertical-align($input-height-small); + } + &.btn-xs { + @include navbar-vertical-align(22); + } +} + +// Text in navbars +// +// Add a class to make any element properly align itself vertically within the navbars. + +.navbar-text { + @include navbar-vertical-align($line-height-computed); + + @media (min-width: $grid-float-breakpoint) { + float: left; + margin-left: $navbar-padding-horizontal; + margin-right: $navbar-padding-horizontal; + } +} + +// Component alignment +// +// Repurpose the pull utilities as their own navbar utilities to avoid specificity +// issues with parents and chaining. Only do this when the navbar is uncollapsed +// though so that navbar contents properly stack and align in mobile. +// +// Declared after the navbar components to ensure more specificity on the margins. + +@media (min-width: $grid-float-breakpoint) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -$navbar-padding-horizontal; + + ~ .navbar-right { + margin-right: 0; + } + } +} + +// Alternate navbars +// -------------------------------------------------- + +// Default navbar +.navbar-default { + background-color: $navbar-default-bg; + border-color: $navbar-default-border; + + .navbar-brand { + color: $navbar-default-brand-color; + &:hover, + &:focus { + color: $navbar-default-brand-hover-color; + background-color: $navbar-default-brand-hover-bg; + } + } + + .navbar-text { + color: $navbar-default-color; + } + + .navbar-nav { + > li > a { + color: $navbar-default-link-color; + + &:hover, + &:focus { + color: $navbar-default-link-hover-color; + background-color: $navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: $navbar-default-link-active-color; + background-color: $navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: $navbar-default-link-disabled-color; + background-color: $navbar-default-link-disabled-bg; + } + } + } + + .navbar-toggle { + border-color: $navbar-default-toggle-border-color; + &:hover, + &:focus { + background-color: $navbar-default-toggle-hover-bg; + } + .icon-bar { + background-color: $navbar-default-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: $navbar-default-border; + } + + // Dropdown menu items + .navbar-nav { + // Remove background color from open dropdown + > .open > a { + &, + &:hover, + &:focus { + background-color: $navbar-default-link-active-bg; + color: $navbar-default-link-active-color; + } + } + + @media (max-width: $grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + > li > a { + color: $navbar-default-link-color; + &:hover, + &:focus { + color: $navbar-default-link-hover-color; + background-color: $navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: $navbar-default-link-active-color; + background-color: $navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: $navbar-default-link-disabled-color; + background-color: $navbar-default-link-disabled-bg; + } + } + } + } + } + + // Links in navbars + // + // Add a class to ensure links outside the navbar nav are colored correctly. + + .navbar-link { + color: $navbar-default-link-color; + &:hover { + color: $navbar-default-link-hover-color; + } + } + + .btn-link { + color: $navbar-default-link-color; + &:hover, + &:focus { + color: $navbar-default-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: $navbar-default-link-disabled-color; + } + } + } +} + +// Inverse navbar + +.navbar-inverse { + background-color: $navbar-inverse-bg; + border-color: $navbar-inverse-border; + + .navbar-brand { + color: $navbar-inverse-brand-color; + &:hover, + &:focus { + color: $navbar-inverse-brand-hover-color; + background-color: $navbar-inverse-brand-hover-bg; + } + } + + .navbar-text { + color: $navbar-inverse-color; + } + + .navbar-nav { + > li > a { + color: $navbar-inverse-link-color; + + &:hover, + &:focus { + color: $navbar-inverse-link-hover-color; + background-color: $navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: $navbar-inverse-link-active-color; + background-color: $navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: $navbar-inverse-link-disabled-color; + background-color: $navbar-inverse-link-disabled-bg; + } + } + } + + // Darken the responsive nav toggle + .navbar-toggle { + border-color: $navbar-inverse-toggle-border-color; + &:hover, + &:focus { + background-color: $navbar-inverse-toggle-hover-bg; + } + .icon-bar { + background-color: $navbar-inverse-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: darken($navbar-inverse-bg, 7%); + } + + // Dropdowns + .navbar-nav { + > .open > a { + &, + &:hover, + &:focus { + background-color: $navbar-inverse-link-active-bg; + color: $navbar-inverse-link-active-color; + } + } + + @media (max-width: $grid-float-breakpoint-max) { + // Dropdowns get custom display + .open .dropdown-menu { + > .dropdown-header { + border-color: $navbar-inverse-border; + } + .divider { + background-color: $navbar-inverse-border; + } + > li > a { + color: $navbar-inverse-link-color; + &:hover, + &:focus { + color: $navbar-inverse-link-hover-color; + background-color: $navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: $navbar-inverse-link-active-color; + background-color: $navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: $navbar-inverse-link-disabled-color; + background-color: $navbar-inverse-link-disabled-bg; + } + } + } + } + } + + .navbar-link { + color: $navbar-inverse-link-color; + &:hover { + color: $navbar-inverse-link-hover-color; + } + } + + .btn-link { + color: $navbar-inverse-link-color; + &:hover, + &:focus { + color: $navbar-inverse-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: $navbar-inverse-link-disabled-color; + } + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_navs.scss b/openecomp-ui/resources/scss/bootstrap/_navs.scss new file mode 100644 index 0000000000..30a609851b --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_navs.scss @@ -0,0 +1,235 @@ +// +// Navs +// -------------------------------------------------- + +// Base class +// -------------------------------------------------- + +.nav { + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + @include clearfix; + + > li { + position: relative; + display: block; + + > a { + position: relative; + display: block; + padding: $nav-link-padding; + &:hover, + &:focus { + text-decoration: none; + background-color: $nav-link-hover-bg; + } + } + + // Disabled state sets text to gray and nukes hover/tab effects + &.disabled > a { + color: $nav-disabled-link-color; + + &:hover, + &:focus { + color: $nav-disabled-link-hover-color; + text-decoration: none; + background-color: transparent; + cursor: $cursor-disabled; + } + } + } + + // Open dropdowns + .open > a { + &, + &:hover, + &:focus { + background-color: $nav-link-hover-bg; + border-color: $link-color; + } + } + + // Nav dividers (deprecated with v3.0.1) + // + // This should have been removed in v3 with the dropping of `.nav-list`, but + // we missed it. We don't currently support this anywhere, but in the interest + // of maintaining backward compatibility in case you use it, it's deprecated. + .nav-divider { + @include nav-divider; + } + + // Prevent IE8 from misplacing imgs + // + // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 + > li > a > img { + max-width: none; + } +} + +// Tabs +// ------------------------- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid $nav-tabs-border-color; + > li { + float: left; + // Make the list-items overlay the bottom border + margin-bottom: -1px; + + // Actual tabs (as links) + > a { + margin-right: 2px; + line-height: $line-height-base; + border: 1px solid transparent; + border-radius: $border-radius-base $border-radius-base 0 0; + &:hover { + border-color: $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-border-color; + } + } + + // Active state, and its :hover to override normal :hover + &.active > a { + &, + &:hover, + &:focus { + color: $nav-tabs-active-link-hover-color; + background-color: $nav-tabs-active-link-hover-bg; + border: 1px solid $nav-tabs-active-link-hover-border-color; + border-bottom-color: transparent; + cursor: default; + } + } + } + // pulling this in mainly for less shorthand + &.nav-justified { + @extend .nav-justified; + @extend .nav-tabs-justified; + } +} + +// Pills +// ------------------------- +.nav-pills { + > li { + float: left; + + // Links rendered as pills + > a { + border-radius: $nav-pills-border-radius; + } + + li { + margin-left: 2px; + } + + // Active state + &.active > a { + &, + &:hover, + &:focus { + color: $nav-pills-active-link-hover-color; + background-color: $nav-pills-active-link-hover-bg; + } + } + } +} + +// Stacked pills +.nav-stacked { + > li { + float: none; + + li { + margin-top: 2px; + margin-left: 0; // no need for this gap between nav items + } + } +} + +// Nav variations +// -------------------------------------------------- + +// Justified nav links +// ------------------------- + +.nav-justified { + width: 100%; + + > li { + float: none; + > a { + text-align: center; + margin-bottom: 5px; + } + } + + > .dropdown .dropdown-menu { + top: auto; + left: auto; + } + + @media (min-width: $screen-sm-min) { + > li { + display: table-cell; + width: 1%; + > a { + margin-bottom: 0; + } + } + } +} + +// Move borders to anchors instead of bottom of list +// +// Mixin for adding on top the shared `.nav-justified` styles for our tabs +.nav-tabs-justified { + border-bottom: 0; + + > li > a { + // Override margin from .nav-tabs + margin-right: 0; + border-radius: $border-radius-base; + } + + > .active > a, + > .active > a:hover, + > .active > a:focus { + border: 1px solid $nav-tabs-justified-link-border-color; + } + + @media (min-width: $screen-sm-min) { + > li > a { + border-bottom: 1px solid $nav-tabs-justified-link-border-color; + border-radius: $border-radius-base $border-radius-base 0 0; + } + > .active > a, + > .active > a:hover, + > .active > a:focus { + border-bottom-color: $nav-tabs-justified-active-link-border-color; + } + } +} + +// Tabbable tabs +// ------------------------- + +// Hide tabbable panes to start, show them when `.active` +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} + +// Dropdowns +// ------------------------- + +// Specific dropdowns +.nav-tabs .dropdown-menu { + // make dropdown border overlap tab border + margin-top: -1px; + // Remove the top rounded corners here since there is a hard edge above the menu + @include border-top-radius(0); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_normalize.scss b/openecomp-ui/resources/scss/bootstrap/_normalize.scss new file mode 100644 index 0000000000..9dddf73ad2 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_normalize.scss @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +// +// 1. Set default font family to sans-serif. +// 2. Prevent iOS and IE text size adjust after device orientation change, +// without disabling user zoom. +// + +html { + font-family: sans-serif; // 1 + -ms-text-size-adjust: 100%; // 2 + -webkit-text-size-adjust: 100%; // 2 +} + +// +// Remove default margin. +// + +body { + margin: 0; +} + +// HTML5 display definitions +// ========================================================================== + +// +// Correct `block` display not defined for any HTML5 element in IE 8/9. +// Correct `block` display not defined for `details` or `summary` in IE 10/11 +// and Firefox. +// Correct `block` display not defined for `main` in IE 11. +// + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +// +// 1. Correct `inline-block` display not defined in IE 8/9. +// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. +// + +audio, +canvas, +progress, +video { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// +// Prevent modern browsers from displaying `audio` without controls. +// Remove excess height in iOS 5 devices. +// + +audio:not([controls]) { + display: none; + height: 0; +} + +// +// Address `[hidden]` styling not present in IE 8/9/10. +// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. +// + +[hidden], +template { + display: none; +} + +// Links +// ========================================================================== + +// +// Remove the gray background color from active links in IE 10. +// + +a { + background-color: transparent; +} + +// +// Improve readability of focused elements when they are also in an +// active/hover state. +// + +a:active, +a:hover { + outline: 0; +} + +// Text-level semantics +// ========================================================================== + +// +// Address styling not present in IE 8/9/10/11, Safari, and Chrome. +// + +abbr[title] { + border-bottom: 1px dotted; +} + +// +// Address style set to `bolder` in Firefox 4+, Safari, and Chrome. +// + +b, +strong { + font-weight: bold; +} + +// +// Address styling not present in Safari and Chrome. +// + +dfn { + font-style: italic; +} + +// +// Address variable `h1` font-size and margin within `section` and `article` +// contexts in Firefox 4+, Safari, and Chrome. +// + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +// +// Address styling not present in IE 8/9. +// + +mark { + background: #ff0; + color: #000; +} + +// +// Address inconsistent and variable font size in all browsers. +// + +small { + font-size: 80%; +} + +// +// Prevent `sub` and `sup` affecting `line-height` in all browsers. +// + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +// Embedded content +// ========================================================================== + +// +// Remove border when inside `a` element in IE 8/9/10. +// + +img { + border: 0; +} + +// +// Correct overflow not hidden in IE 9/10/11. +// + +svg:not(:root) { + overflow: hidden; +} + +// Grouping content +// ========================================================================== + +// +// Address margin not present in IE 8/9 and Safari. +// + +figure { + margin: 1em 40px; +} + +// +// Address differences between Firefox and other browsers. +// + +hr { + box-sizing: content-box; + height: 0; +} + +// +// Contain overflow in all browsers. +// + +pre { + overflow: auto; +} + +// +// Address odd `em`-unit font size rendering in all browsers. +// + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +// Forms +// ========================================================================== + +// +// Known limitation: by default, Chrome and Safari on OS X allow very limited +// styling of `select`, unless a `border` property is set. +// + +// +// 1. Correct color not being inherited. +// Known issue: affects color of disabled elements. +// 2. Correct font properties not being inherited. +// 3. Address margins set differently in Firefox 4+, Safari, and Chrome. +// + +button, +input, +optgroup, +select, +textarea { + color: inherit; // 1 + font: inherit; // 2 + margin: 0; // 3 +} + +// +// Address `overflow` set to `hidden` in IE 8/9/10/11. +// + +button { + overflow: visible; +} + +// +// Address inconsistent `text-transform` inheritance for `button` and `select`. +// All other form control elements do not inherit `text-transform` values. +// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. +// Correct `select` style inheritance in Firefox. +// + +button, +select { + text-transform: none; +} + +// +// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` +// and `video` controls. +// 2. Correct inability to style clickable `input` types in iOS. +// 3. Improve usability and consistency of cursor style between image-type +// `input` and others. +// + +button, +html input[type="button"], // 1 +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // 2 + cursor: pointer; // 3 +} + +// +// Re-set default cursor for disabled elements. +// + +button[disabled], +html input[disabled] { + cursor: default; +} + +// +// Remove inner padding and border in Firefox 4+. +// + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +// +// Address Firefox 4+ setting `line-height` on `input` using `!important` in +// the UA stylesheet. +// + +input { + line-height: normal; +} + +// +// It's recommended that you don't attempt to style these elements. +// Firefox's implementation doesn't respect box-sizing, padding, or width. +// +// 1. Address box sizing set to `content-box` in IE 8/9/10. +// 2. Remove excess padding in IE 8/9/10. +// + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// +// Fix the cursor style for Chrome's increment/decrement buttons. For certain +// `font-size` values of the `input`, it causes the cursor style of the +// decrement button to change from `default` to `text`. +// + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// +// 1. Address `appearance` set to `searchfield` in Safari and Chrome. +// 2. Address `box-sizing` set to `border-box` in Safari and Chrome. +// + +input[type="search"] { + -webkit-appearance: textfield; // 1 + box-sizing: content-box; //2 +} + +// +// Remove inner padding and search cancel button in Safari and Chrome on OS X. +// Safari (but not Chrome) clips the cancel button when the search input has +// padding (and `textfield` appearance). +// + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// +// Define consistent border, margin, and padding. +// + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +// +// 1. Correct `color` not being inherited in IE 8/9/10/11. +// 2. Remove padding so people aren't caught out if they zero out fieldsets. +// + +legend { + border: 0; // 1 + padding: 0; // 2 +} + +// +// Remove default vertical scrollbar in IE 8/9/10/11. +// + +textarea { + overflow: auto; +} + +// +// Don't inherit the `font-weight` (applied by a rule above). +// NOTE: the default cannot safely be changed in Chrome and Safari on OS X. +// + +optgroup { + font-weight: bold; +} + +// Tables +// ========================================================================== + +// +// Remove most spacing between table cells. +// + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_pager.scss b/openecomp-ui/resources/scss/bootstrap/_pager.scss new file mode 100644 index 0000000000..5799c9bd54 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_pager.scss @@ -0,0 +1,53 @@ +// +// Pager pagination +// -------------------------------------------------- + +.pager { + padding-left: 0; + margin: $line-height-computed 0; + list-style: none; + text-align: center; + @include clearfix; + li { + display: inline; + > a, + > span { + display: inline-block; + padding: 5px 14px; + background-color: $pager-bg; + border: 1px solid $pager-border; + border-radius: $pager-border-radius; + } + + > a:hover, + > a:focus { + text-decoration: none; + background-color: $pager-hover-bg; + } + } + + .next { + > a, + > span { + float: right; + } + } + + .previous { + > a, + > span { + float: left; + } + } + + .disabled { + > a, + > a:hover, + > a:focus, + > span { + color: $pager-disabled-color; + background-color: $pager-bg; + cursor: $cursor-disabled; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_pagination.scss b/openecomp-ui/resources/scss/bootstrap/_pagination.scss new file mode 100644 index 0000000000..882cee1f0d --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_pagination.scss @@ -0,0 +1,89 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- +.pagination { + display: inline-block; + padding-left: 0; + margin: $line-height-computed 0; + border-radius: $border-radius-base; + + > li { + display: inline; // Remove list-style and block-level defaults + > a, + > span { + position: relative; + float: left; // Collapse white-space + padding: $padding-base-vertical $padding-base-horizontal; + line-height: $line-height-base; + text-decoration: none; + color: $pagination-color; + background-color: $pagination-bg; + border: 1px solid $pagination-border; + margin-left: -1px; + } + &:first-child { + > a, + > span { + margin-left: 0; + @include border-left-radius($border-radius-base); + } + } + &:last-child { + > a, + > span { + @include border-right-radius($border-radius-base); + } + } + } + + > li > a, + > li > span { + &:hover, + &:focus { + z-index: 3; + color: $pagination-hover-color; + background-color: $pagination-hover-bg; + border-color: $pagination-hover-border; + } + } + + > .active > a, + > .active > span { + &, + &:hover, + &:focus { + z-index: 2; + color: $pagination-active-color; + background-color: $pagination-active-bg; + border-color: $pagination-active-border; + cursor: default; + } + } + + > .disabled { + > span, + > span:hover, + > span:focus, + > a, + > a:hover, + > a:focus { + color: $pagination-disabled-color; + background-color: $pagination-disabled-bg; + border-color: $pagination-disabled-border; + cursor: $cursor-disabled; + } + } +} + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-lg { + @include pagination-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $border-radius-large); +} + +// Small +.pagination-sm { + @include pagination-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $border-radius-small); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_panels.scss b/openecomp-ui/resources/scss/bootstrap/_panels.scss new file mode 100644 index 0000000000..f37b58147b --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_panels.scss @@ -0,0 +1,274 @@ +// +// Panels +// -------------------------------------------------- + +// Base class +.panel { + margin-bottom: $line-height-computed; + background-color: $panel-bg; + border: 1px solid transparent; + border-radius: $panel-border-radius; + @include box-shadow(0 1px 1px rgba(0, 0, 0, .05)); +} + +// Panel contents +.panel-body { + padding: $panel-body-padding; + @include clearfix; +} + +// Optional heading +.panel-heading { + padding: $panel-heading-padding; + border-bottom: 1px solid transparent; + @include border-top-radius(($panel-border-radius - 1)); + + > .dropdown .dropdown-toggle { + color: inherit; + } +} + +// Within heading, strip any `h*` tag of its default margins for spacing. +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: ceil(($font-size-base * 1.125)); + color: inherit; + + > a, + > small, + > .small, + > small > a, + > .small > a { + color: inherit; + } +} + +// Optional footer (stays gray in every modifier class) +.panel-footer { + padding: $panel-footer-padding; + background-color: $panel-footer-bg; + border-top: 1px solid $panel-inner-border; + @include border-bottom-radius(($panel-border-radius - 1)); +} + +// List groups in panels +// +// By default, space out list group content from panel headings to account for +// any kind of custom content between the two. + +.panel { + > .list-group, + > .panel-collapse > .list-group { + margin-bottom: 0; + + .list-group-item { + border-width: 1px 0; + border-radius: 0; + } + + // Add border top radius for first one + &:first-child { + .list-group-item:first-child { + border-top: 0; + @include border-top-radius(($panel-border-radius - 1)); + } + } + + // Add border bottom radius for last one + &:last-child { + .list-group-item:last-child { + border-bottom: 0; + @include border-bottom-radius(($panel-border-radius - 1)); + } + } + } + > .panel-heading + .panel-collapse > .list-group { + .list-group-item:first-child { + @include border-top-radius(0); + } + } +} + +// Collapse space between when there's no additional content. +.panel-heading + .list-group { + .list-group-item:first-child { + border-top-width: 0; + } +} + +.list-group + .panel-footer { + border-top-width: 0; +} + +// Tables in panels +// +// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and +// watch it go full width. + +.panel { + > .table, + > .table-responsive > .table, + > .panel-collapse > .table { + margin-bottom: 0; + + caption { + padding-left: $panel-body-padding; + padding-right: $panel-body-padding; + } + } + // Add border top radius for first one + > .table:first-child, + > .table-responsive:first-child > .table:first-child { + @include border-top-radius(($panel-border-radius - 1)); + + > thead:first-child, + > tbody:first-child { + > tr:first-child { + border-top-left-radius: ($panel-border-radius - 1); + border-top-right-radius: ($panel-border-radius - 1); + + td:first-child, + th:first-child { + border-top-left-radius: ($panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-top-right-radius: ($panel-border-radius - 1); + } + } + } + } + // Add border bottom radius for last one + > .table:last-child, + > .table-responsive:last-child > .table:last-child { + @include border-bottom-radius(($panel-border-radius - 1)); + + > tbody:last-child, + > tfoot:last-child { + > tr:last-child { + border-bottom-left-radius: ($panel-border-radius - 1); + border-bottom-right-radius: ($panel-border-radius - 1); + + td:first-child, + th:first-child { + border-bottom-left-radius: ($panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-bottom-right-radius: ($panel-border-radius - 1); + } + } + } + } + > .panel-body + .table, + > .panel-body + .table-responsive, + > .table + .panel-body, + > .table-responsive + .panel-body { + border-top: 1px solid $table-border-color; + } + > .table > tbody:first-child > tr:first-child th, + > .table > tbody:first-child > tr:first-child td { + border-top: 0; + } + > .table-bordered, + > .table-responsive > .table-bordered { + border: 0; + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + > thead, + > tbody { + > tr:first-child { + > td, + > th { + border-bottom: 0; + } + } + } + > tbody, + > tfoot { + > tr:last-child { + > td, + > th { + border-bottom: 0; + } + } + } + } + > .table-responsive { + border: 0; + margin-bottom: 0; + } +} + +// Collapsable panels (aka, accordion) +// +// Wrap a series of panels in `.panel-group` to turn them into an accordion with +// the help of our collapse JavaScript plugin. + +.panel-group { + margin-bottom: $line-height-computed; + + // Tighten up margin so it's only between panels + .panel { + margin-bottom: 0; + border-radius: $panel-border-radius; + + + .panel { + margin-top: 5px; + } + } + + .panel-heading { + border-bottom: 0; + + + .panel-collapse > .panel-body, + + .panel-collapse > .list-group { + border-top: 1px solid $panel-inner-border; + } + } + + .panel-footer { + border-top: 0; + + .panel-collapse .panel-body { + border-bottom: 1px solid $panel-inner-border; + } + } +} + +// Contextual variations +.panel-default { + @include panel-variant($panel-default-border, $panel-default-text, $panel-default-heading-bg, $panel-default-border); +} + +.panel-primary { + @include panel-variant($panel-primary-border, $panel-primary-text, $panel-primary-heading-bg, $panel-primary-border); +} + +.panel-success { + @include panel-variant($panel-success-border, $panel-success-text, $panel-success-heading-bg, $panel-success-border); +} + +.panel-info { + @include panel-variant($panel-info-border, $panel-info-text, $panel-info-heading-bg, $panel-info-border); +} + +.panel-warning { + @include panel-variant($panel-warning-border, $panel-warning-text, $panel-warning-heading-bg, $panel-warning-border); +} + +.panel-danger { + @include panel-variant($panel-danger-border, $panel-danger-text, $panel-danger-heading-bg, $panel-danger-border); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_popovers.scss b/openecomp-ui/resources/scss/bootstrap/_popovers.scss new file mode 100644 index 0000000000..73d0bf27bf --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_popovers.scss @@ -0,0 +1,140 @@ +// +// Popovers +// -------------------------------------------------- + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: $zindex-popover; + display: none; + max-width: $popover-max-width; + padding: 1px; + // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text; + font-size: $font-size-base; + + background-color: $popover-bg; + background-clip: padding-box; + border: 1px solid $popover-fallback-border-color; + border: 1px solid $popover-border-color; + border-radius: $border-radius-large; + @include box-shadow(0 5px 10px rgba(0, 0, 0, .2)); + + // Offset the popover to account for the popover arrow + &.top { + margin-top: -$popover-arrow-width; + } + &.right { + margin-left: $popover-arrow-width; + } + &.bottom { + margin-top: $popover-arrow-width; + } + &.left { + margin-left: -$popover-arrow-width; + } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: $font-size-base; + background-color: $popover-title-bg; + border-bottom: 1px solid darken($popover-title-bg, 5%); + border-radius: ($border-radius-large - 1) ($border-radius-large - 1) 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover > .arrow { + &, + &:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } +} + +.popover > .arrow { + border-width: $popover-arrow-outer-width; +} + +.popover > .arrow:after { + border-width: $popover-arrow-width; + content: ""; +} + +.popover { + &.top > .arrow { + left: 50%; + margin-left: -$popover-arrow-outer-width; + border-bottom-width: 0; + border-top-color: $popover-arrow-outer-fallback-color; // IE8 fallback + border-top-color: $popover-arrow-outer-color; + bottom: -$popover-arrow-outer-width; + &:after { + content: " "; + bottom: 1px; + margin-left: -$popover-arrow-width; + border-bottom-width: 0; + border-top-color: $popover-arrow-color; + } + } + &.right > .arrow { + top: 50%; + left: -$popover-arrow-outer-width; + margin-top: -$popover-arrow-outer-width; + border-left-width: 0; + border-right-color: $popover-arrow-outer-fallback-color; // IE8 fallback + border-right-color: $popover-arrow-outer-color; + &:after { + content: " "; + left: 1px; + bottom: -$popover-arrow-width; + border-left-width: 0; + border-right-color: $popover-arrow-color; + } + } + &.bottom > .arrow { + left: 50%; + margin-left: -$popover-arrow-outer-width; + border-top-width: 0; + border-bottom-color: $popover-arrow-outer-fallback-color; // IE8 fallback + border-bottom-color: $popover-arrow-outer-color; + top: -$popover-arrow-outer-width; + &:after { + content: " "; + top: 1px; + margin-left: -$popover-arrow-width; + border-top-width: 0; + border-bottom-color: $popover-arrow-color; + } + } + + &.left > .arrow { + top: 50%; + right: -$popover-arrow-outer-width; + margin-top: -$popover-arrow-outer-width; + border-right-width: 0; + border-left-color: $popover-arrow-outer-fallback-color; // IE8 fallback + border-left-color: $popover-arrow-outer-color; + &:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: $popover-arrow-color; + bottom: -$popover-arrow-width; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_print.scss b/openecomp-ui/resources/scss/bootstrap/_print.scss new file mode 100644 index 0000000000..fa05d1a007 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_print.scss @@ -0,0 +1,101 @@ +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: h5bp.com/r +// ========================================================================== + +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + // Bootstrap specific changes start + + // Bootstrap components + .navbar { + display: none; + } + .btn, + .dropup > .btn { + > .caret { + border-top-color: #000 !important; + } + } + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + + td, + th { + background-color: #fff !important; + } + } + .table-bordered { + th, + td { + border: 1px solid #ddd !important; + } + } + + // Bootstrap specific changes end +} diff --git a/openecomp-ui/resources/scss/bootstrap/_progress-bars.scss b/openecomp-ui/resources/scss/bootstrap/_progress-bars.scss new file mode 100644 index 0000000000..d99fc49dec --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_progress-bars.scss @@ -0,0 +1,92 @@ +// +// Progress bars +// -------------------------------------------------- + +// Bar animations +// ------------------------- + +// WebKit +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +// Spec and IE10+ +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +// Bar itself +// ------------------------- + +// Outer container +.progress { + overflow: hidden; + height: $line-height-computed; + margin-bottom: $line-height-computed; + background-color: $progress-bg; + border-radius: $progress-border-radius; + @include box-shadow(inset 0 1px 2px rgba(0, 0, 0, .1)); +} + +// Bar of progress +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: $font-size-small; + line-height: $line-height-computed; + color: $progress-bar-color; + text-align: center; + background-color: $progress-bar-bg; + @include box-shadow(inset 0 -1px 0 rgba(0, 0, 0, .15)); + @include transition(width .6s ease); +} + +// Striped bars +// +// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar-striped` class, which you just add to an existing +// `.progress-bar`. +.progress-striped .progress-bar, +.progress-bar-striped { + @include gradient-striped; + background-size: 40px 40px; +} + +// Call animation for the active one +// +// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar.active` approach. +.progress.active .progress-bar, +.progress-bar.active { + @include animation(progress-bar-stripes 2s linear infinite); +} + +// Variations +// ------------------------- + +.progress-bar-success { + @include progress-bar-variant($progress-bar-success-bg); +} + +.progress-bar-info { + @include progress-bar-variant($progress-bar-info-bg); +} + +.progress-bar-warning { + @include progress-bar-variant($progress-bar-warning-bg); +} + +.progress-bar-danger { + @include progress-bar-variant($progress-bar-danger-bg); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_responsive-embed.scss b/openecomp-ui/resources/scss/bootstrap/_responsive-embed.scss new file mode 100644 index 0000000000..282589f97c --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_responsive-embed.scss @@ -0,0 +1,35 @@ +// Embeds responsive +// +// Credit: Nicolas Gallagher and SUIT CSS. + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; + + .embed-responsive-item, + iframe, + embed, + object, + video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; + } +} + +// Modifier class for 16:9 aspect ratio +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} + +// Modifier class for 4:3 aspect ratio +.embed-responsive-4by3 { + padding-bottom: 75%; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_responsive-utilities.scss b/openecomp-ui/resources/scss/bootstrap/_responsive-utilities.scss new file mode 100644 index 0000000000..915d723599 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_responsive-utilities.scss @@ -0,0 +1,191 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + +// IE10 in Windows (Phone) 8 +// +// Support for responsive views via media queries is kind of borked in IE10, for +// Surface/desktop in split view and for Windows Phone 8. This particular fix +// must be accompanied by a snippet of JavaScript to sniff the user agent and +// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at +// our Getting Started page for more information on this bug. +// +// For more information, see the following: +// +// Issue: https://github.com/twbs/bootstrap/issues/10497 +// Docs: http://getbootstrap.com/getting-started/#support-ie10-width +// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@at-root { + @-ms-viewport { + width: device-width; + } +} + +// Visibility utilities +// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0 + +@include responsive-invisibility('.visible-xs'); +@include responsive-invisibility('.visible-sm'); +@include responsive-invisibility('.visible-md'); +@include responsive-invisibility('.visible-lg'); + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} + +@media (max-width: $screen-xs-max) { + @include responsive-visibility('.visible-xs'); +} + +.visible-xs-block { + @media (max-width: $screen-xs-max) { + display: block !important; + } +} + +.visible-xs-inline { + @media (max-width: $screen-xs-max) { + display: inline !important; + } +} + +.visible-xs-inline-block { + @media (max-width: $screen-xs-max) { + display: inline-block !important; + } +} + +@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + @include responsive-visibility('.visible-sm'); +} + +.visible-sm-block { + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + display: block !important; + } +} + +.visible-sm-inline { + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + display: inline !important; + } +} + +.visible-sm-inline-block { + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + display: inline-block !important; + } +} + +@media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + @include responsive-visibility('.visible-md'); +} + +.visible-md-block { + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + display: block !important; + } +} + +.visible-md-inline { + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + display: inline !important; + } +} + +.visible-md-inline-block { + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + display: inline-block !important; + } +} + +@media (min-width: $screen-lg-min) { + @include responsive-visibility('.visible-lg'); +} + +.visible-lg-block { + @media (min-width: $screen-lg-min) { + display: block !important; + } +} + +.visible-lg-inline { + @media (min-width: $screen-lg-min) { + display: inline !important; + } +} + +.visible-lg-inline-block { + @media (min-width: $screen-lg-min) { + display: inline-block !important; + } +} + +@media (max-width: $screen-xs-max) { + @include responsive-invisibility('.hidden-xs'); +} + +@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + @include responsive-invisibility('.hidden-sm'); +} + +@media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + @include responsive-invisibility('.hidden-md'); +} + +@media (min-width: $screen-lg-min) { + @include responsive-invisibility('.hidden-lg'); +} + +// Print utilities +// +// Media queries are placed on the inside to be mixin-friendly. + +// Note: Deprecated .visible-print as of v3.2.0 + +@include responsive-invisibility('.visible-print'); + +@media print { + @include responsive-visibility('.visible-print'); +} + +.visible-print-block { + display: none !important; + + @media print { + display: block !important; + } +} + +.visible-print-inline { + display: none !important; + + @media print { + display: inline !important; + } +} + +.visible-print-inline-block { + display: none !important; + + @media print { + display: inline-block !important; + } +} + +@media print { + @include responsive-invisibility('.hidden-print'); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_scaffolding.scss b/openecomp-ui/resources/scss/bootstrap/_scaffolding.scss new file mode 100644 index 0000000000..910afd55e5 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_scaffolding.scss @@ -0,0 +1,154 @@ +// +// Scaffolding +// -------------------------------------------------- + +// Reset the box-sizing +// +// Heads up! This reset may cause conflicts with some third-party widgets. +// For recommendations on resolving such conflicts, see +// http://getbootstrap.com/getting-started/#third-box-sizing +* { + @include box-sizing(border-box); +} + +*:before, +*:after { + @include box-sizing(border-box); +} + +// Body reset + +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: $font-family-base; + font-size: $font-size-base; + line-height: $line-height-base; + color: $text-color; + background-color: $body-bg; +} + +// Reset fonts for relevant elements +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +// Links + +a { + color: $link-color; + text-decoration: none; + + &:hover, + &:focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + } + + &:focus { + @include tab-focus; + } +} + +// Figures +// +// We reset this here because previously Normalize had no `figure` margins. This +// ensures we don't break anyone's use of the element. + +figure { + margin: 0; +} + +// Images + +img { + vertical-align: middle; +} + +// Responsive images (ensure images don't scale beyond their parents) +.img-responsive { + @include img-responsive; +} + +// Rounded corners +.img-rounded { + border-radius: $border-radius-large; +} + +// Image thumbnails +// +// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. +.img-thumbnail { + padding: $thumbnail-padding; + line-height: $line-height-base; + background-color: $thumbnail-bg; + border: 1px solid $thumbnail-border; + border-radius: $thumbnail-border-radius; + @include transition(all .2s ease-in-out); + + // Keep them at most 100% wide + @include img-responsive(inline-block); +} + +// Perfect circle +.img-circle { + border-radius: 50%; // set radius in percents +} + +// Horizontal rules + +hr { + margin-top: $line-height-computed; + margin-bottom: $line-height-computed; + border: 0; + border-top: 1px solid $hr-border; +} + +// Only display content to screen readers +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// Credit: HTML5 Boilerplate + +.sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} + +// iOS "clickable elements" fix for role="button" +// +// Fixes "clickability" issue (and more generally, the firing of events such as focus as well) +// for traditionally non-focusable elements with role="button" +// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +[role="button"] { + cursor: pointer; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_tables.scss b/openecomp-ui/resources/scss/bootstrap/_tables.scss new file mode 100644 index 0000000000..39379abba2 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_tables.scss @@ -0,0 +1,228 @@ +// +// Tables +// -------------------------------------------------- + +table { + background-color: $table-bg; +} + +caption { + padding-top: $table-cell-padding; + padding-bottom: $table-cell-padding; + color: $text-muted; + text-align: left; +} + +th { + text-align: left; +} + +// Baseline styles + +.table { + width: 100%; + max-width: 100%; + margin-bottom: $line-height-computed; + // Cells + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: $table-cell-padding; + line-height: $line-height-base; + vertical-align: top; + border-top: 1px solid $table-border-color; + } + } + } + // Bottom align for column headings + > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid $table-border-color; + } + // Remove top border from thead by default + > caption + thead, + > colgroup + thead, + > thead:first-child { + > tr:first-child { + > th, + > td { + border-top: 0; + } + } + } + // Account for multiple tbody instances + > tbody + tbody { + border-top: 2px solid $table-border-color; + } + + // Nesting + .table { + background-color: $body-bg; + } +} + +// Condensed table w/ half padding + +.table-condensed { + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: $table-condensed-cell-padding; + } + } + } +} + +// Bordered version +// +// Add borders all around the table and between all the columns. + +.table-bordered { + border: 1px solid $table-border-color; + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + border: 1px solid $table-border-color; + } + } + } + > thead > tr { + > th, + > td { + border-bottom-width: 2px; + } + } +} + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +.table-striped { + > tbody > tr:nth-of-type(odd) { + background-color: $table-bg-accent; + } +} + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + > tbody > tr:hover { + background-color: $table-bg-hover; + } +} + +// Table cell sizing +// +// Reset default table behavior + +table col[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-column; +} + +table { + td, + th { + &[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-cell; + } + } +} + +// Table backgrounds +// +// Exact selectors below required to override `.table-striped` and prevent +// inheritance to nested tables. + +// Generate the contextual variants +@include table-row-variant('active', $table-bg-active); +@include table-row-variant('success', $state-success-bg); +@include table-row-variant('info', $state-info-bg); +@include table-row-variant('warning', $state-warning-bg); +@include table-row-variant('danger', $state-danger-bg); + +// Responsive tables +// +// Wrap your tables in `.table-responsive` and we'll make them mobile friendly +// by enabling horizontal scrolling. Only applies <768px. Everything above that +// will display normally. + +.table-responsive { + overflow-x: auto; + min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837) + + @media screen and (max-width: $screen-xs-max) { + width: 100%; + margin-bottom: ($line-height-computed * 0.75); + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid $table-border-color; + + // Tighten up spacing + > .table { + margin-bottom: 0; + + // Ensure the content doesn't wrap + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + white-space: nowrap; + } + } + } + } + + // Special overrides for the bordered tables + > .table-bordered { + border: 0; + + // Nuke the appropriate borders so that the parent can handle them + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + + // Only nuke the last row's bottom-border in `tbody` and `tfoot` since + // chances are there will be only one `tr` in a `thead` and that would + // remove the border altogether. + > tbody, + > tfoot { + > tr:last-child { + > th, + > td { + border-bottom: 0; + } + } + } + + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_theme.scss b/openecomp-ui/resources/scss/bootstrap/_theme.scss new file mode 100644 index 0000000000..dfec418770 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_theme.scss @@ -0,0 +1,344 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +// +// Load core variables and mixins +// -------------------------------------------------- +@import "variables"; +@import "mixins"; + +// +// Buttons +// -------------------------------------------------- + +// Common styles +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); + $shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + @include box-shadow($shadow); + + // Reset the shadow + &:active, + &.active { + @include box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + @include box-shadow(none); + } + + .badge { + text-shadow: none; + } +} + +// Mixin for generating new styles +@mixin btn-styles($btn-color: #555) { + @include gradient-vertical($start-color: $btn-color, $end-color: darken($btn-color, 12%)); + @include reset-filter; // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620 + background-repeat: repeat-x; + border-color: darken($btn-color, 14%); + + &:hover, + &:focus { + background-color: darken($btn-color, 12%); + background-position: 0 -15px; + } + + &:active, + &.active { + background-color: darken($btn-color, 12%); + border-color: darken($btn-color, 14%); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &.focus, + &:active, + &.active { + background-color: darken($btn-color, 12%); + background-image: none; + } + } +} + +// Common styles +.btn { + // Remove the gradient for the pressed/active state + &:active, + &.active { + background-image: none; + } +} + +// Apply the mixin to the buttons +.btn-default { + @include btn-styles($btn-default-bg); + text-shadow: 0 1px 0 #fff; + border-color: #ccc; +} + +.btn-primary { + @include btn-styles($btn-primary-bg); +} + +.btn-success { + @include btn-styles($btn-success-bg); +} + +.btn-info { + @include btn-styles($btn-info-bg); +} + +.btn-warning { + @include btn-styles($btn-warning-bg); +} + +.btn-danger { + @include btn-styles($btn-danger-bg); +} + +// +// Images +// -------------------------------------------------- + +.thumbnail, +.img-thumbnail { + @include box-shadow(0 1px 2px rgba(0, 0, 0, .075)); +} + +// +// Dropdowns +// -------------------------------------------------- + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + @include gradient-vertical($start-color: $dropdown-link-hover-bg, $end-color: darken($dropdown-link-hover-bg, 5%)); + background-color: darken($dropdown-link-hover-bg, 5%); +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + @include gradient-vertical($start-color: $dropdown-link-active-bg, $end-color: darken($dropdown-link-active-bg, 5%)); + background-color: darken($dropdown-link-active-bg, 5%); +} + +// +// Navbar +// -------------------------------------------------- + +// Default navbar +.navbar-default { + @include gradient-vertical($start-color: lighten($navbar-default-bg, 10%), $end-color: $navbar-default-bg); + @include reset-filter; // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered + border-radius: $navbar-border-radius; + $shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); + @include box-shadow($shadow); + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + @include gradient-vertical($start-color: darken($navbar-default-link-active-bg, 5%), $end-color: darken($navbar-default-link-active-bg, 2%)); + @include box-shadow(inset 0 3px 9px rgba(0, 0, 0, .075)); + } +} + +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} + +// Inverted navbar +.navbar-inverse { + @include gradient-vertical($start-color: lighten($navbar-inverse-bg, 10%), $end-color: $navbar-inverse-bg); + @include reset-filter; // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257 + border-radius: $navbar-border-radius; + .navbar-nav > .open > a, + .navbar-nav > .active > a { + @include gradient-vertical($start-color: $navbar-inverse-link-active-bg, $end-color: lighten($navbar-inverse-link-active-bg, 2.5%)); + @include box-shadow(inset 0 3px 9px rgba(0, 0, 0, .25)); + } + + .navbar-brand, + .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); + } +} + +// Undo rounded corners in static and fixed navbars +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} + +// Fix active state of dropdown items in collapsed mode +@media (max-width: $grid-float-breakpoint-max) { + .navbar .navbar-nav .open .dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: #fff; + @include gradient-vertical($start-color: $dropdown-link-active-bg, $end-color: darken($dropdown-link-active-bg, 5%)); + } + } +} + +// +// Alerts +// -------------------------------------------------- + +// Common styles +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, .2); + $shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + @include box-shadow($shadow); +} + +// Mixin for generating new styles +@mixin alert-styles($color) { + @include gradient-vertical($start-color: $color, $end-color: darken($color, 7.5%)); + border-color: darken($color, 15%); +} + +// Apply the mixin to the alerts +.alert-success { + @include alert-styles($alert-success-bg); +} + +.alert-info { + @include alert-styles($alert-info-bg); +} + +.alert-warning { + @include alert-styles($alert-warning-bg); +} + +.alert-danger { + @include alert-styles($alert-danger-bg); +} + +// +// Progress bars +// -------------------------------------------------- + +// Give the progress background some depth +.progress { + @include gradient-vertical($start-color: darken($progress-bg, 4%), $end-color: $progress-bg) +} + +// Mixin for generating new styles +@mixin progress-bar-styles($color) { + @include gradient-vertical($start-color: $color, $end-color: darken($color, 10%)); +} + +// Apply the mixin to the progress bars +.progress-bar { + @include progress-bar-styles($progress-bar-bg); +} + +.progress-bar-success { + @include progress-bar-styles($progress-bar-success-bg); +} + +.progress-bar-info { + @include progress-bar-styles($progress-bar-info-bg); +} + +.progress-bar-warning { + @include progress-bar-styles($progress-bar-warning-bg); +} + +.progress-bar-danger { + @include progress-bar-styles($progress-bar-danger-bg); +} + +// Reset the striped class because our mixins don't do multiple gradients and +// the above custom styles override the new `.progress-bar-striped` in v3.2.0. +.progress-bar-striped { + @include gradient-striped; +} + +// +// List groups +// -------------------------------------------------- + +.list-group { + border-radius: $border-radius-base; + @include box-shadow(0 1px 2px rgba(0, 0, 0, .075)); +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 darken($list-group-active-bg, 10%); + @include gradient-vertical($start-color: $list-group-active-bg, $end-color: darken($list-group-active-bg, 7.5%)); + border-color: darken($list-group-active-border, 7.5%); + + .badge { + text-shadow: none; + } +} + +// +// Panels +// -------------------------------------------------- + +// Common styles +.panel { + @include box-shadow(0 1px 2px rgba(0, 0, 0, .05)); +} + +// Mixin for generating new styles +@mixin panel-heading-styles($color) { + @include gradient-vertical($start-color: $color, $end-color: darken($color, 5%)); +} + +// Apply the mixin to the panel headings only +.panel-default > .panel-heading { + @include panel-heading-styles($panel-default-heading-bg); +} + +.panel-primary > .panel-heading { + @include panel-heading-styles($panel-primary-heading-bg); +} + +.panel-success > .panel-heading { + @include panel-heading-styles($panel-success-heading-bg); +} + +.panel-info > .panel-heading { + @include panel-heading-styles($panel-info-heading-bg); +} + +.panel-warning > .panel-heading { + @include panel-heading-styles($panel-warning-heading-bg); +} + +.panel-danger > .panel-heading { + @include panel-heading-styles($panel-danger-heading-bg); +} + +// +// Wells +// -------------------------------------------------- + +.well { + @include gradient-vertical($start-color: darken($well-bg, 5%), $end-color: $well-bg); + border-color: darken($well-bg, 10%); + $shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); + @include box-shadow($shadow); +} diff --git a/openecomp-ui/resources/scss/bootstrap/_thumbnails.scss b/openecomp-ui/resources/scss/bootstrap/_thumbnails.scss new file mode 100644 index 0000000000..ec4a9cbf97 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_thumbnails.scss @@ -0,0 +1,37 @@ +// +// Thumbnails +// -------------------------------------------------- + +// Mixin and adjust the regular image class +.thumbnail { + display: block; + padding: $thumbnail-padding; + margin-bottom: $line-height-computed; + line-height: $line-height-base; + background-color: $thumbnail-bg; + border: 1px solid $thumbnail-border; + border-radius: $thumbnail-border-radius; + @include transition(border .2s ease-in-out); + + > img, + a > img { + @include img-responsive; + margin-left: auto; + margin-right: auto; + } + + // [converter] extracted a&:hover, a&:focus, a&.active to a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active + + // Image captions + .caption { + padding: $thumbnail-caption-padding; + color: $thumbnail-caption-color; + } +} + +// Add a hover state for linked versions only +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: $link-color; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_tooltip.scss b/openecomp-ui/resources/scss/bootstrap/_tooltip.scss new file mode 100644 index 0000000000..8a7d8856bc --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_tooltip.scss @@ -0,0 +1,115 @@ +// +// Tooltips +// -------------------------------------------------- + +// Base class +.tooltip { + position: absolute; + z-index: $zindex-tooltip; + display: block; + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text; + font-size: $font-size-small; + + @include opacity(0); + + &.in { + @include opacity($tooltip-opacity); + } + &.top { + margin-top: -3px; + padding: $tooltip-arrow-width 0; + } + &.right { + margin-left: 3px; + padding: 0 $tooltip-arrow-width; + } + &.bottom { + margin-top: 3px; + padding: $tooltip-arrow-width 0; + } + &.left { + margin-left: -3px; + padding: 0 $tooltip-arrow-width; + } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: $tooltip-max-width; + padding: 3px 8px; + color: $tooltip-color; + text-align: center; + background-color: $tooltip-bg; + border-radius: $border-radius-base; +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1 +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -$tooltip-arrow-width; + border-width: $tooltip-arrow-width $tooltip-arrow-width 0; + border-top-color: $tooltip-arrow-color; + } + &.top-left .tooltip-arrow { + bottom: 0; + right: $tooltip-arrow-width; + margin-bottom: -$tooltip-arrow-width; + border-width: $tooltip-arrow-width $tooltip-arrow-width 0; + border-top-color: $tooltip-arrow-color; + } + &.top-right .tooltip-arrow { + bottom: 0; + left: $tooltip-arrow-width; + margin-bottom: -$tooltip-arrow-width; + border-width: $tooltip-arrow-width $tooltip-arrow-width 0; + border-top-color: $tooltip-arrow-color; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -$tooltip-arrow-width; + border-width: $tooltip-arrow-width $tooltip-arrow-width $tooltip-arrow-width 0; + border-right-color: $tooltip-arrow-color; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -$tooltip-arrow-width; + border-width: $tooltip-arrow-width 0 $tooltip-arrow-width $tooltip-arrow-width; + border-left-color: $tooltip-arrow-color; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -$tooltip-arrow-width; + border-width: 0 $tooltip-arrow-width $tooltip-arrow-width; + border-bottom-color: $tooltip-arrow-color; + } + &.bottom-left .tooltip-arrow { + top: 0; + right: $tooltip-arrow-width; + margin-top: -$tooltip-arrow-width; + border-width: 0 $tooltip-arrow-width $tooltip-arrow-width; + border-bottom-color: $tooltip-arrow-color; + } + &.bottom-right .tooltip-arrow { + top: 0; + left: $tooltip-arrow-width; + margin-top: -$tooltip-arrow-width; + border-width: 0 $tooltip-arrow-width $tooltip-arrow-width; + border-bottom-color: $tooltip-arrow-color; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/_type.scss b/openecomp-ui/resources/scss/bootstrap/_type.scss new file mode 100644 index 0000000000..8fb97e4798 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_type.scss @@ -0,0 +1,339 @@ +// +// Typography +// -------------------------------------------------- + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: $headings-font-family; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: $headings-color; + + small, + .small { + font-weight: normal; + line-height: 1; + color: $headings-small-color; + } +} + +h1, .h1, +h2, .h2, +h3, .h3 { + margin-top: $line-height-computed; + margin-bottom: ($line-height-computed / 2); + + small, + .small { + font-size: 65%; + } +} + +h4, .h4, +h5, .h5, +h6, .h6 { + margin-top: ($line-height-computed / 2); + margin-bottom: ($line-height-computed / 2); + + small, + .small { + font-size: 75%; + } +} + +h1, .h1 { + font-size: $font-size-h1; +} + +h2, .h2 { + font-size: $font-size-h2; +} + +h3, .h3 { + font-size: $font-size-h3; +} + +h4, .h4 { + font-size: $font-size-h4; +} + +h5, .h5 { + font-size: $font-size-h5; +} + +h6, .h6 { + font-size: $font-size-h6; +} + +// Body text +// ------------------------- + +p { + margin: 0 0 ($line-height-computed / 2); +} + +.lead { + margin-bottom: $line-height-computed; + font-size: floor(($font-size-base * 1.15)); + font-weight: 300; + line-height: 1.4; + + @media (min-width: $screen-sm-min) { + font-size: ($font-size-base * 1.5); + } +} + +// Emphasis & misc +// ------------------------- + +// Ex: (12px small font / 14px base font) * 100% = about 85% +small, +.small { + font-size: floor((100% * $font-size-small / $font-size-base)); +} + +mark, +.mark { + background-color: $state-warning-bg; + padding: .2em; +} + +// Alignment +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +.text-justify { + text-align: justify; +} + +.text-nowrap { + white-space: nowrap; +} + +// Transformation +.text-lowercase { + text-transform: lowercase; +} + +.text-uppercase { + text-transform: uppercase; +} + +.text-capitalize { + text-transform: capitalize; +} + +// Contextual colors +.text-muted { + color: $text-muted; +} + +@include text-emphasis-variant('.text-primary', $brand-primary); + +@include text-emphasis-variant('.text-success', $state-success-text); + +@include text-emphasis-variant('.text-info', $state-info-text); + +@include text-emphasis-variant('.text-warning', $state-warning-text); + +@include text-emphasis-variant('.text-danger', $state-danger-text); + +// Contextual backgrounds +// For now we'll leave these alongside the text classes until v4 when we can +// safely shift things around (per SemVer rules). +.bg-primary { + // Given the contrast here, this is the only class to have its color inverted + // automatically. + color: #fff; +} + +@include bg-variant('.bg-primary', $brand-primary); + +@include bg-variant('.bg-success', $state-success-bg); + +@include bg-variant('.bg-info', $state-info-bg); + +@include bg-variant('.bg-warning', $state-warning-bg); + +@include bg-variant('.bg-danger', $state-danger-bg); + +// Page header +// ------------------------- + +.page-header { + padding-bottom: (($line-height-computed / 2) - 1); + margin: ($line-height-computed * 2) 0 $line-height-computed; + border-bottom: 1px solid $page-header-border-color; +} + +// Lists +// ------------------------- + +// Unordered and Ordered lists +ul, +ol { + margin-top: 0; + margin-bottom: ($line-height-computed / 2); + ul, + ol { + margin-bottom: 0; + } +} + +// List options + +// [converter] extracted from `.list-unstyled` for libsass compatibility +@mixin list-unstyled { + padding-left: 0; + list-style: none; +} + +// [converter] extracted as `@mixin list-unstyled` for libsass compatibility +.list-unstyled { + @include list-unstyled; +} + +// Inline turns list items into inline-block +.list-inline { + @include list-unstyled; + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-top: 0; // Remove browser default + margin-bottom: $line-height-computed; +} + +dt, +dd { + line-height: $line-height-base; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; // Undo browser default +} + +// Horizontal description lists +// +// Defaults to being stacked without any of the below styles applied, until the +// grid breakpoint is reached (default of ~768px). + +.dl-horizontal { + dd { + @include clearfix; // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: $grid-float-breakpoint) { + dt { + float: left; + width: ($dl-horizontal-offset - 20); + clear: left; + text-align: right; + @include text-overflow; + } + dd { + margin-left: $dl-horizontal-offset; + } + } +} + +// Misc +// ------------------------- + +// Abbreviations and acronyms +abbr[title], + // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted $abbr-border-color; +} + +.initialism { + font-size: 90%; + @extend .text-uppercase; +} + +// Blockquotes +blockquote { + padding: ($line-height-computed / 2) $line-height-computed; + margin: 0 0 $line-height-computed; + font-size: $blockquote-font-size; + border-left: 5px solid $blockquote-border-color; + + p, + ul, + ol { + &:last-child { + margin-bottom: 0; + } + } + + // Note: Deprecated small and .small as of v3.1.0 + // Context: https://github.com/twbs/bootstrap/issues/11660 + footer, + small, + .small { + display: block; + font-size: 80%; // back to default font-size + line-height: $line-height-base; + color: $blockquote-small-color; + + &:before { + content: '\2014 \00A0'; // em dash, nbsp + } + } +} + +// Opposite alignment of blockquote +// +// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid $blockquote-border-color; + border-left: 0; + text-align: right; + + // Account for citation + footer, + small, + .small { + &:before { + content: ''; + } + &:after { + content: '\00A0 \2014'; // nbsp, em dash + } + } +} + +// Addresses +address { + margin-bottom: $line-height-computed; + font-style: normal; + line-height: $line-height-base; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_utilities.scss b/openecomp-ui/resources/scss/bootstrap/_utilities.scss new file mode 100644 index 0000000000..137720136b --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_utilities.scss @@ -0,0 +1,57 @@ +// +// Utility classes +// -------------------------------------------------- + +// Floats +// ------------------------- + +.clearfix { + @include clearfix; +} + +.center-block { + @include center-block; +} + +.pull-right { + float: right !important; +} + +.pull-left { + float: left !important; +} + +// Toggling content +// ------------------------- + +// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 +.hide { + display: none !important; +} + +.show { + display: block !important; +} + +.invisible { + visibility: hidden; +} + +.text-hide { + @include text-hide; +} + +// Hide from screenreaders and browsers +// +// Credit: HTML5 Boilerplate + +.hidden { + display: none !important; +} + +// For Affix plugin +// ------------------------- + +.affix { + position: fixed; +} diff --git a/openecomp-ui/resources/scss/bootstrap/_variables.scss b/openecomp-ui/resources/scss/bootstrap/_variables.scss new file mode 100644 index 0000000000..50b8781a3d --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_variables.scss @@ -0,0 +1,852 @@ +$bootstrap-sass-asset-helper: false !default; +// +// Variables +// -------------------------------------------------- + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +$gray-base: #000 !default; +$gray-darker: lighten($gray-base, 13.5%) !default; +// #222 +$gray-dark: lighten($gray-base, 20%) !default; +// #333 +$gray: lighten($gray-base, 33.5%) !default; +// #555 +$gray-light: lighten($gray-base, 46.7%) !default; +// #777 +$gray-lighter: lighten($gray-base, 93.5%) !default; +// #eee + +$brand-primary: darken(#428bca, 6.5%) !default; +// #337ab7 +$brand-success: #5cb85c !default; +$brand-info: #5bc0de !default; +$brand-warning: #f0ad4e !default; +$brand-danger: #d9534f !default; + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for `<body>`. +$body-bg: #fff !default; +//** Global text color on `<body>`. +$text-color: $gray-dark !default; + +//** Global textual link color. +$link-color: $brand-primary !default; +//** Link hover color set via `darken()` function. +$link-hover-color: darken($link-color, 15%) !default; +//** Link hover decoration. +$link-hover-decoration: underline !default; + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +$font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif !default; +$font-family-serif: Georgia, "Times New Roman", Times, serif !default; +//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`. +$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default; +$font-family-base: $font-family-sans-serif !default; + +$font-size-base: 14px !default; +$font-size-large: ceil(($font-size-base * 1.25)) !default; +// ~18px +$font-size-small: ceil(($font-size-base * 0.85)) !default; +// ~12px + +$font-size-h1: floor(($font-size-base * 2.6)) !default; +// ~36px +$font-size-h2: floor(($font-size-base * 2.15)) !default; +// ~30px +$font-size-h3: ceil(($font-size-base * 1.7)) !default; +// ~24px +$font-size-h4: ceil(($font-size-base * 1.25)) !default; +// ~18px +$font-size-h5: $font-size-base !default; +$font-size-h6: ceil(($font-size-base * 0.85)) !default; +// ~12px + +//** Unit-less `line-height` for use in components like buttons. +$line-height-base: 1.428571429 !default; +// 20/14 +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. +$line-height-computed: floor(($font-size-base * $line-height-base)) !default; +// ~20px + +//** By default, this inherits from the `<body>`. +$headings-font-family: inherit !default; +$headings-font-weight: 500 !default; +$headings-line-height: 1.1 !default; +$headings-color: inherit !default; + +//== Iconography +// +//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower. + +//** Load fonts from this directory. + +// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path. +// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths. +$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/") !default; + +//** File name for all font files. +$icon-font-name: "glyphicons-halflings-regular" !default; +//** Element ID within SVG icon file. +$icon-font-svg-id: "glyphicons_halflingsregular" !default; + +//== Components +// +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). + +$padding-base-vertical: 6px !default; +$padding-base-horizontal: 12px !default; + +$padding-large-vertical: 10px !default; +$padding-large-horizontal: 16px !default; + +$padding-small-vertical: 5px !default; +$padding-small-horizontal: 10px !default; + +$padding-xs-vertical: 1px !default; +$padding-xs-horizontal: 5px !default; + +$line-height-large: 1.3333333 !default; +// extra decimals for Win 8.1 Chrome +$line-height-small: 1.5 !default; + +$border-radius-base: 4px !default; +$border-radius-large: 6px !default; +$border-radius-small: 3px !default; + +//** Global color for active items (e.g., navs or dropdowns). +$component-active-color: #fff !default; +//** Global background color for active items (e.g., navs or dropdowns). +$component-active-bg: $brand-primary !default; + +//** Width of the `border` for generating carets that indicator dropdowns. +$caret-width-base: 4px !default; +//** Carets increase slightly in size for larger components. +$caret-width-large: 5px !default; + +//== Tables +// +//## Customizes the `.table` component with basic values, each used across all table variations. + +//** Padding for `<th>`s and `<td>`s. +$table-cell-padding: 8px !default; +//** Padding for cells in `.table-condensed`. +$table-condensed-cell-padding: 5px !default; + +//** Default background color used for all tables. +$table-bg: transparent !default; +//** Background color used for `.table-striped`. +$table-bg-accent: #f9f9f9 !default; +//** Background color used for `.table-hover`. +$table-bg-hover: #f5f5f5 !default; +$table-bg-active: $table-bg-hover !default; + +//** Border color for table and cell borders. +$table-border-color: #ddd !default; + +//== Buttons +// +//## For each of Bootstrap's buttons, define text, background and border color. + +$btn-font-weight: normal !default; + +$btn-default-color: #333 !default; +$btn-default-bg: #fff !default; +$btn-default-border: #ccc !default; + +$btn-primary-color: #fff !default; +$btn-primary-bg: $brand-primary !default; +$btn-primary-border: darken($btn-primary-bg, 5%) !default; + +$btn-success-color: #fff !default; +$btn-success-bg: $brand-success !default; +$btn-success-border: darken($btn-success-bg, 5%) !default; + +$btn-info-color: #fff !default; +$btn-info-bg: $brand-info !default; +$btn-info-border: darken($btn-info-bg, 5%) !default; + +$btn-warning-color: #fff !default; +$btn-warning-bg: $brand-warning !default; +$btn-warning-border: darken($btn-warning-bg, 5%) !default; + +$btn-danger-color: #fff !default; +$btn-danger-bg: $brand-danger !default; +$btn-danger-border: darken($btn-danger-bg, 5%) !default; + +$btn-link-disabled-color: $gray-light !default; + +// Allows for customizing button radius independently from global border radius +$btn-border-radius-base: $border-radius-base !default; +$btn-border-radius-large: $border-radius-large !default; +$btn-border-radius-small: $border-radius-small !default; + +//== Forms +// +//## + +//** `<input>` background color +$input-bg: #fff !default; +//** `<input disabled>` background color +$input-bg-disabled: $gray-lighter !default; + +//** Text color for `<input>`s +$input-color: $gray !default; +//** `<input>` border color +$input-border: #ccc !default; + +// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4 +//** Default `.form-control` border radius +// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS. +$input-border-radius: $border-radius-base !default; +//** Large `.form-control` border radius +$input-border-radius-large: $border-radius-large !default; +//** Small `.form-control` border radius +$input-border-radius-small: $border-radius-small !default; + +//** Border color for inputs on focus +$input-border-focus: #66afe9 !default; + +//** Placeholder text color +$input-color-placeholder: #999 !default; + +//** Default `.form-control` height +$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default; +//** Large `.form-control` height +$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default; +//** Small `.form-control` height +$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default; + +//** `.form-group` margin +$form-group-margin-bottom: 15px !default; + +$legend-color: $gray-dark !default; +$legend-border-color: #e5e5e5 !default; + +//** Background color for textual input addons +$input-group-addon-bg: $gray-lighter !default; +//** Border color for textual input addons +$input-group-addon-border-color: $input-border !default; + +//** Disabled cursor for form controls and buttons. +$cursor-disabled: not-allowed !default; + +//== Dropdowns +// +//## Dropdown menu container and contents. + +//** Background for the dropdown menu. +$dropdown-bg: #fff !default; +//** Dropdown menu `border-color`. +$dropdown-border: rgba(0, 0, 0, .15) !default; +//** Dropdown menu `border-color` **for IE8**. +$dropdown-fallback-border: #ccc !default; +//** Divider color for between dropdown items. +$dropdown-divider-bg: #e5e5e5 !default; + +//** Dropdown link text color. +$dropdown-link-color: $gray-dark !default; +//** Hover color for dropdown links. +$dropdown-link-hover-color: darken($gray-dark, 5%) !default; +//** Hover background for dropdown links. +$dropdown-link-hover-bg: #f5f5f5 !default; + +//** Active dropdown menu item text color. +$dropdown-link-active-color: $component-active-color !default; +//** Active dropdown menu item background color. +$dropdown-link-active-bg: $component-active-bg !default; + +//** Disabled dropdown menu item background color. +$dropdown-link-disabled-color: $gray-light !default; + +//** Text color for headers within dropdown menus. +$dropdown-header-color: $gray-light !default; + +//** Deprecated `$dropdown-caret-color` as of v3.1.0 +$dropdown-caret-color: #000 !default; + +//-- Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. +// +// Note: These variables are not generated into the Customizer. + +$zindex-navbar: 1000 !default; +$zindex-dropdown: 1000 !default; +$zindex-popover: 1060 !default; +$zindex-tooltip: 1070 !default; +$zindex-navbar-fixed: 1030 !default; +$zindex-modal-background: 1040 !default; +$zindex-modal: 1050 !default; + +//== Media queries breakpoints +// +//## Define the breakpoints at which your layout will change, adapting to different screen sizes. + +// Extra small screen / phone +//** Deprecated `$screen-xs` as of v3.0.1 +$screen-xs: 480px !default; +//** Deprecated `$screen-xs-min` as of v3.2.0 +$screen-xs-min: $screen-xs !default; +//** Deprecated `$screen-phone` as of v3.0.1 +$screen-phone: $screen-xs-min !default; + +// Small screen / tablet +//** Deprecated `$screen-sm` as of v3.0.1 +$screen-sm: 768px !default; +$screen-sm-min: $screen-sm !default; +//** Deprecated `$screen-tablet` as of v3.0.1 +$screen-tablet: $screen-sm-min !default; + +// Medium screen / desktop +//** Deprecated `$screen-md` as of v3.0.1 +$screen-md: 992px !default; +$screen-md-min: $screen-md !default; +//** Deprecated `$screen-desktop` as of v3.0.1 +$screen-desktop: $screen-md-min !default; + +// Large screen / wide desktop +//** Deprecated `$screen-lg` as of v3.0.1 +$screen-lg: 1200px !default; +$screen-lg-min: $screen-lg !default; +//** Deprecated `$screen-lg-desktop` as of v3.0.1 +$screen-lg-desktop: $screen-lg-min !default; + +// So media queries don't overlap when required, provide a maximum +$screen-xs-max: ($screen-sm-min - 1) !default; +$screen-sm-max: ($screen-md-min - 1) !default; +$screen-md-max: ($screen-lg-min - 1) !default; + +//== Grid system +// +//## Define your custom responsive grid. + +//** Number of columns in the grid. +$grid-columns: 12 !default; +//** Padding between columns. Gets divided in half for the left and right. +$grid-gutter-width: 30px !default; +// Navbar collapse +//** Point at which the navbar becomes uncollapsed. +$grid-float-breakpoint: $screen-sm-min !default; +//** Point at which the navbar begins collapsing. +$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default; + +//== Container sizes +// +//## Define the maximum width of `.container` for different screen sizes. + +// Small screen / tablet +$container-tablet: (720px + $grid-gutter-width) !default; +//** For `$screen-sm-min` and up. +$container-sm: $container-tablet !default; + +// Medium screen / desktop +$container-desktop: (940px + $grid-gutter-width) !default; +//** For `$screen-md-min` and up. +$container-md: $container-desktop !default; + +// Large screen / wide desktop +$container-large-desktop: (1140px + $grid-gutter-width) !default; +//** For `$screen-lg-min` and up. +$container-lg: $container-large-desktop !default; + +//== Navbar +// +//## + +// Basics of a navbar +$navbar-height: 50px !default; +$navbar-margin-bottom: $line-height-computed !default; +$navbar-border-radius: $border-radius-base !default; +$navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default; +$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default; +$navbar-collapse-max-height: 340px !default; + +$navbar-default-color: #777 !default; +$navbar-default-bg: #f8f8f8 !default; +$navbar-default-border: darken($navbar-default-bg, 6.5%) !default; + +// Navbar links +$navbar-default-link-color: #777 !default; +$navbar-default-link-hover-color: #333 !default; +$navbar-default-link-hover-bg: transparent !default; +$navbar-default-link-active-color: #555 !default; +$navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) !default; +$navbar-default-link-disabled-color: #ccc !default; +$navbar-default-link-disabled-bg: transparent !default; + +// Navbar brand label +$navbar-default-brand-color: $navbar-default-link-color !default; +$navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default; +$navbar-default-brand-hover-bg: transparent !default; + +// Navbar toggle +$navbar-default-toggle-hover-bg: #ddd !default; +$navbar-default-toggle-icon-bar-bg: #888 !default; +$navbar-default-toggle-border-color: #ddd !default; + +//=== Inverted navbar +// Reset inverted navbar basics +$navbar-inverse-color: lighten($gray-light, 15%) !default; +$navbar-inverse-bg: #222 !default; +$navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default; + +// Inverted navbar links +$navbar-inverse-link-color: lighten($gray-light, 15%) !default; +$navbar-inverse-link-hover-color: #fff !default; +$navbar-inverse-link-hover-bg: transparent !default; +$navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default; +$navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) !default; +$navbar-inverse-link-disabled-color: #444 !default; +$navbar-inverse-link-disabled-bg: transparent !default; + +// Inverted navbar brand label +$navbar-inverse-brand-color: $navbar-inverse-link-color !default; +$navbar-inverse-brand-hover-color: #fff !default; +$navbar-inverse-brand-hover-bg: transparent !default; + +// Inverted navbar toggle +$navbar-inverse-toggle-hover-bg: #333 !default; +$navbar-inverse-toggle-icon-bar-bg: #fff !default; +$navbar-inverse-toggle-border-color: #333 !default; + +//== Navs +// +//## + +//=== Shared nav styles +$nav-link-padding: 10px 15px !default; +$nav-link-hover-bg: $gray-lighter !default; + +$nav-disabled-link-color: $gray-light !default; +$nav-disabled-link-hover-color: $gray-light !default; + +//== Tabs +$nav-tabs-border-color: #ddd !default; + +$nav-tabs-link-hover-border-color: $gray-lighter !default; + +$nav-tabs-active-link-hover-bg: $body-bg !default; +$nav-tabs-active-link-hover-color: $gray !default; +$nav-tabs-active-link-hover-border-color: #ddd !default; + +$nav-tabs-justified-link-border-color: #ddd !default; +$nav-tabs-justified-active-link-border-color: $body-bg !default; + +//== Pills +$nav-pills-border-radius: $border-radius-base !default; +$nav-pills-active-link-hover-bg: $component-active-bg !default; +$nav-pills-active-link-hover-color: $component-active-color !default; + +//== Pagination +// +//## + +$pagination-color: $link-color !default; +$pagination-bg: #fff !default; +$pagination-border: #ddd !default; + +$pagination-hover-color: $link-hover-color !default; +$pagination-hover-bg: $gray-lighter !default; +$pagination-hover-border: #ddd !default; + +$pagination-active-color: #fff !default; +$pagination-active-bg: $brand-primary !default; +$pagination-active-border: $brand-primary !default; + +$pagination-disabled-color: $gray-light !default; +$pagination-disabled-bg: #fff !default; +$pagination-disabled-border: #ddd !default; + +//== Pager +// +//## + +$pager-bg: $pagination-bg !default; +$pager-border: $pagination-border !default; +$pager-border-radius: 15px !default; + +$pager-hover-bg: $pagination-hover-bg !default; + +$pager-active-bg: $pagination-active-bg !default; +$pager-active-color: $pagination-active-color !default; + +$pager-disabled-color: $pagination-disabled-color !default; + +//== Jumbotron +// +//## + +$jumbotron-padding: 30px !default; +$jumbotron-color: inherit !default; +$jumbotron-bg: $gray-lighter !default; +$jumbotron-heading-color: inherit !default; +$jumbotron-font-size: ceil(($font-size-base * 1.5)) !default; +$jumbotron-heading-font-size: ceil(($font-size-base * 4.5)) !default; + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + +$state-success-text: #3c763d !default; +$state-success-bg: #dff0d8 !default; +$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default; + +$state-info-text: #31708f !default; +$state-info-bg: #d9edf7 !default; +$state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default; + +$state-warning-text: #8a6d3b !default; +$state-warning-bg: #fcf8e3 !default; +$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default; + +$state-danger-text: #a94442 !default; +$state-danger-bg: #f2dede !default; +$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default; + +//== Tooltips +// +//## + +//** Tooltip max width +$tooltip-max-width: 200px !default; +//** Tooltip text color +$tooltip-color: #fff !default; +//** Tooltip background color +$tooltip-bg: #000 !default; +$tooltip-opacity: .9 !default; + +//** Tooltip arrow width +$tooltip-arrow-width: 5px !default; +//** Tooltip arrow color +$tooltip-arrow-color: $tooltip-bg !default; + +//== Popovers +// +//## + +//** Popover body background color +$popover-bg: #fff !default; +//** Popover maximum width +$popover-max-width: 276px !default; +//** Popover border color +$popover-border-color: rgba(0, 0, 0, .2) !default; +//** Popover fallback border color +$popover-fallback-border-color: #ccc !default; + +//** Popover title background color +$popover-title-bg: darken($popover-bg, 3%) !default; + +//** Popover arrow width +$popover-arrow-width: 10px !default; +//** Popover arrow color +$popover-arrow-color: $popover-bg !default; + +//** Popover outer arrow width +$popover-arrow-outer-width: ($popover-arrow-width + 1) !default; +//** Popover outer arrow color +$popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !default; +//** Popover outer arrow fallback color +$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default; + +//== Labels +// +//## + +//** Default label background color +$label-default-bg: $gray-light !default; +//** Primary label background color +$label-primary-bg: $brand-primary !default; +//** Success label background color +$label-success-bg: $brand-success !default; +//** Info label background color +$label-info-bg: $brand-info !default; +//** Warning label background color +$label-warning-bg: $brand-warning !default; +//** Danger label background color +$label-danger-bg: $brand-danger !default; + +//** Default label text color +$label-color: #fff !default; +//** Default text color of a linked label +$label-link-hover-color: #fff !default; + +//== Modals +// +//## + +//** Padding applied to the modal body +$modal-inner-padding: 15px !default; + +//** Padding applied to the modal title +$modal-title-padding: 15px !default; +//** Modal title line-height +$modal-title-line-height: $line-height-base !default; + +//** Background color of modal content area +$modal-content-bg: #fff !default; +//** Modal content border color +$modal-content-border-color: rgba(0, 0, 0, .2) !default; +//** Modal content border color **for IE8** +$modal-content-fallback-border-color: #999 !default; + +//** Modal backdrop background color +$modal-backdrop-bg: #000 !default; +//** Modal backdrop opacity +$modal-backdrop-opacity: .5 !default; +//** Modal header border color +$modal-header-border-color: #e5e5e5 !default; +//** Modal footer border color +$modal-footer-border-color: $modal-header-border-color !default; + +$modal-lg: 900px !default; +$modal-md: 600px !default; +$modal-sm: 300px !default; + +//== Alerts +// +//## Define alert colors, border radius, and padding. + +$alert-padding: 15px !default; +$alert-border-radius: $border-radius-base !default; +$alert-link-font-weight: bold !default; + +$alert-success-bg: $state-success-bg !default; +$alert-success-text: $state-success-text !default; +$alert-success-border: $state-success-border !default; + +$alert-info-bg: $state-info-bg !default; +$alert-info-text: $state-info-text !default; +$alert-info-border: $state-info-border !default; + +$alert-warning-bg: $state-warning-bg !default; +$alert-warning-text: $state-warning-text !default; +$alert-warning-border: $state-warning-border !default; + +$alert-danger-bg: $state-danger-bg !default; +$alert-danger-text: $state-danger-text !default; +$alert-danger-border: $state-danger-border !default; + +//== Progress bars +// +//## + +//** Background color of the whole progress component +$progress-bg: #f5f5f5 !default; +//** Progress bar text color +$progress-bar-color: #fff !default; +//** Variable for setting rounded corners on progress bar. +$progress-border-radius: $border-radius-base !default; + +//** Default progress bar color +$progress-bar-bg: $brand-primary !default; +//** Success progress bar color +$progress-bar-success-bg: $brand-success !default; +//** Warning progress bar color +$progress-bar-warning-bg: $brand-warning !default; +//** Danger progress bar color +$progress-bar-danger-bg: $brand-danger !default; +//** Info progress bar color +$progress-bar-info-bg: $brand-info !default; + +//== List group +// +//## + +//** Background color on `.list-group-item` +$list-group-bg: #fff !default; +//** `.list-group-item` border color +$list-group-border: #ddd !default; +//** List group border radius +$list-group-border-radius: $border-radius-base !default; + +//** Background color of single list items on hover +$list-group-hover-bg: #f5f5f5 !default; +//** Text color of active list items +$list-group-active-color: $component-active-color !default; +//** Background color of active list items +$list-group-active-bg: $component-active-bg !default; +//** Border color of active list elements +$list-group-active-border: $list-group-active-bg !default; +//** Text color for content within active list items +$list-group-active-text-color: lighten($list-group-active-bg, 40%) !default; + +//** Text color of disabled list items +$list-group-disabled-color: $gray-light !default; +//** Background color of disabled list items +$list-group-disabled-bg: $gray-lighter !default; +//** Text color for content within disabled list items +$list-group-disabled-text-color: $list-group-disabled-color !default; + +$list-group-link-color: #555 !default; +$list-group-link-hover-color: $list-group-link-color !default; +$list-group-link-heading-color: #333 !default; + +//== Panels +// +//## + +$panel-bg: #fff !default; +$panel-body-padding: 15px !default; +$panel-heading-padding: 10px 15px !default; +$panel-footer-padding: $panel-heading-padding !default; +$panel-border-radius: $border-radius-base !default; + +//** Border color for elements within panels +$panel-inner-border: #ddd !default; +$panel-footer-bg: #f5f5f5 !default; + +$panel-default-text: $gray-dark !default; +$panel-default-border: #ddd !default; +$panel-default-heading-bg: #f5f5f5 !default; + +$panel-primary-text: #fff !default; +$panel-primary-border: $brand-primary !default; +$panel-primary-heading-bg: $brand-primary !default; + +$panel-success-text: $state-success-text !default; +$panel-success-border: $state-success-border !default; +$panel-success-heading-bg: $state-success-bg !default; + +$panel-info-text: $state-info-text !default; +$panel-info-border: $state-info-border !default; +$panel-info-heading-bg: $state-info-bg !default; + +$panel-warning-text: $state-warning-text !default; +$panel-warning-border: $state-warning-border !default; +$panel-warning-heading-bg: $state-warning-bg !default; + +$panel-danger-text: $state-danger-text !default; +$panel-danger-border: $state-danger-border !default; +$panel-danger-heading-bg: $state-danger-bg !default; + +//== Thumbnails +// +//## + +//** Padding around the thumbnail image +$thumbnail-padding: 4px !default; +//** Thumbnail background color +$thumbnail-bg: $body-bg !default; +//** Thumbnail border color +$thumbnail-border: #ddd !default; +//** Thumbnail border radius +$thumbnail-border-radius: $border-radius-base !default; + +//** Custom text color for thumbnail captions +$thumbnail-caption-color: $text-color !default; +//** Padding around the thumbnail caption +$thumbnail-caption-padding: 9px !default; + +//== Wells +// +//## + +$well-bg: #f5f5f5 !default; +$well-border: darken($well-bg, 7%) !default; + +//== Badges +// +//## + +$badge-color: #fff !default; +//** Linked badge text color on hover +$badge-link-hover-color: #fff !default; +$badge-bg: $gray-light !default; + +//** Badge text color in active nav link +$badge-active-color: $link-color !default; +//** Badge background color in active nav link +$badge-active-bg: #fff !default; + +$badge-font-weight: bold !default; +$badge-line-height: 1 !default; +$badge-border-radius: 10px !default; + +//== Breadcrumbs +// +//## + +$breadcrumb-padding-vertical: 8px !default; +$breadcrumb-padding-horizontal: 15px !default; +//** Breadcrumb background color +$breadcrumb-bg: #f5f5f5 !default; +//** Breadcrumb text color +$breadcrumb-color: #ccc !default; +//** Text color of current page in the breadcrumb +$breadcrumb-active-color: $gray-light !default; +//** Textual separator for between breadcrumb elements +$breadcrumb-separator: "/" !default; + +//== Carousel +// +//## + +$carousel-text-shadow: 0 1px 2px rgba(0, 0, 0, .6) !default; + +$carousel-control-color: #fff !default; +$carousel-control-width: 15% !default; +$carousel-control-opacity: .5 !default; +$carousel-control-font-size: 20px !default; + +$carousel-indicator-active-bg: #fff !default; +$carousel-indicator-border-color: #fff !default; + +$carousel-caption-color: #fff !default; + +//== Close +// +//## + +$close-font-weight: bold !default; +$close-color: #000 !default; +$close-text-shadow: 0 1px 0 #fff !default; + +//== Code +// +//## + +$code-color: #c7254e !default; +$code-bg: #f9f2f4 !default; + +$kbd-color: #fff !default; +$kbd-bg: #333 !default; + +$pre-bg: #f5f5f5 !default; +$pre-color: $gray-dark !default; +$pre-border-color: #ccc !default; +$pre-scrollable-max-height: 340px !default; + +//== Type +// +//## + +//** Horizontal offset for forms and lists. +$component-offset-horizontal: 180px !default; +//** Text muted color +$text-muted: $gray-light !default; +//** Abbreviations and acronyms border color +$abbr-border-color: $gray-light !default; +//** Headings small color +$headings-small-color: $gray-light !default; +//** Blockquote small color +$blockquote-small-color: $gray-light !default; +//** Blockquote font size +$blockquote-font-size: ($font-size-base * 1.25) !default; +//** Blockquote border color +$blockquote-border-color: $gray-lighter !default; +//** Page header border color +$page-header-border-color: $gray-lighter !default; +//** Width of horizontal description list titles +$dl-horizontal-offset: $component-offset-horizontal !default; +//** Horizontal line color. +$hr-border: $gray-lighter !default; diff --git a/openecomp-ui/resources/scss/bootstrap/_wells.scss b/openecomp-ui/resources/scss/bootstrap/_wells.scss new file mode 100644 index 0000000000..8d13cb67d3 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/_wells.scss @@ -0,0 +1,29 @@ +// +// Wells +// -------------------------------------------------- + +// Base class +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: $well-bg; + border: 1px solid $well-border; + border-radius: $border-radius-base; + @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, .05)); + blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); + } +} + +// Sizes +.well-lg { + padding: 24px; + border-radius: $border-radius-large; +} + +.well-sm { + padding: 9px; + border-radius: $border-radius-small; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_alerts.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_alerts.scss new file mode 100644 index 0000000000..b092e85eb4 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_alerts.scss @@ -0,0 +1,14 @@ +// Alerts + +@mixin alert-variant($background, $border, $text-color) { + background-color: $background; + border-color: $border; + color: $text-color; + + hr { + border-top-color: darken($border, 5%); + } + .alert-link { + color: darken($text-color, 10%); + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_background-variant.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_background-variant.scss new file mode 100644 index 0000000000..533ff12c84 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_background-variant.scss @@ -0,0 +1,12 @@ +// Contextual backgrounds + +// [converter] $parent hack +@mixin bg-variant($parent, $color) { + #{$parent} { + background-color: $color; + } + a#{$parent}:hover, + a#{$parent}:focus { + background-color: darken($color, 10%); + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_border-radius.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_border-radius.scss new file mode 100644 index 0000000000..30e065daf8 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_border-radius.scss @@ -0,0 +1,21 @@ +// Single side border-radius + +@mixin border-top-radius($radius) { + border-top-right-radius: $radius; + border-top-left-radius: $radius; +} + +@mixin border-right-radius($radius) { + border-bottom-right-radius: $radius; + border-top-right-radius: $radius; +} + +@mixin border-bottom-radius($radius) { + border-bottom-right-radius: $radius; + border-bottom-left-radius: $radius; +} + +@mixin border-left-radius($radius) { + border-bottom-left-radius: $radius; + border-top-left-radius: $radius; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_buttons.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_buttons.scss new file mode 100644 index 0000000000..255c259ddb --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_buttons.scss @@ -0,0 +1,68 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +@mixin button-variant($color, $background, $border) { + color: $color; + background-color: $background; + border-color: $border; + + &:focus, + &.focus { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 25%); + } + &:hover { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 12%); + } + &:active, + &.active, + .open > &.dropdown-toggle { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 12%); + + &:hover, + &:focus, + &.focus { + color: $color; + background-color: darken($background, 17%); + border-color: darken($border, 25%); + } + } + &:active, + &.active, + .open > &.dropdown-toggle { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &.focus, + &:active, + &.active { + background-color: $background; + border-color: $border; + } + } + + .badge { + color: $background; + background-color: $color; + } +} + +// Button sizes +@mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + border-radius: $border-radius; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_center-block.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_center-block.scss new file mode 100644 index 0000000000..e06fb5e276 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_center-block.scss @@ -0,0 +1,7 @@ +// Center-align a block level element + +@mixin center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_clearfix.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_clearfix.scss new file mode 100644 index 0000000000..8042d1823b --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_clearfix.scss @@ -0,0 +1,22 @@ +// Clearfix +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +// +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ + +@mixin clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_forms.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_forms.scss new file mode 100644 index 0000000000..7a26de5515 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_forms.scss @@ -0,0 +1,87 @@ +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +@mixin form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline, + &.radio label, + &.checkbox label, + &.radio-inline label, + &.checkbox-inline label { + color: $text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: $border-color; + @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); // Redeclare so transitions work + &:focus { + border-color: darken($border-color, 10%); + $shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px lighten($border-color, 20%); + @include box-shadow($shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: $text-color; + border-color: $border-color; + background-color: $background-color; + } + // Optional feedback icon + .form-control-feedback { + color: $text-color; + } +} + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `$input-border-focus` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. +@mixin form-control-focus($color: $input-border-focus) { + $color-rgba: rgba(red($color), green($color), blue($color), .6); + &:focus { + border-color: $color; + outline: 0; + @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px $color-rgba); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `<select>` +// element gets special love because it's special, and that's a fact! +// [converter] $parent hack +@mixin input-size($parent, $input-height, $padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + #{$parent} { + height: $input-height; + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + border-radius: $border-radius; + } + + select#{$parent} { + height: $input-height; + line-height: $input-height; + } + + textarea#{$parent}, + select[multiple]#{$parent} { + height: auto; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_gradients.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_gradients.scss new file mode 100644 index 0000000000..f75caf31d5 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_gradients.scss @@ -0,0 +1,60 @@ +// Gradients + +// Horizontal gradient, from left to right +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +// Color stops are not available in IE9 and below. +@mixin gradient-horizontal($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: -webkit-linear-gradient(left, $start-color $start-percent, $end-color $end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(left, $start-color $start-percent, $end-color $end-percent); // Opera 12 + background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9 and down +} + +// Vertical gradient, from top to bottom +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +// Color stops are not available in IE9 and below. +@mixin gradient-vertical($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: -webkit-linear-gradient(top, $start-color $start-percent, $end-color $end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(top, $start-color $start-percent, $end-color $end-percent); // Opera 12 + background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 and down +} + +@mixin gradient-directional($start-color: #555, $end-color: #333, $deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient($deg, $start-color, $end-color); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient($deg, $start-color, $end-color); // Opera 12 + background-image: linear-gradient($deg, $start-color, $end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ +} + +@mixin gradient-horizontal-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, $start-color, $mid-color $color-stop, $end-color); + background-image: -o-linear-gradient(left, $start-color, $mid-color $color-stop, $end-color); + background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9 and down, gets no color-stop at all for proper fallback +} + +@mixin gradient-vertical-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: -webkit-linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-image: -o-linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 and down, gets no color-stop at all for proper fallback +} + +@mixin gradient-radial($inner-color: #555, $outer-color: #333) { + background-image: -webkit-radial-gradient(circle, $inner-color, $outer-color); + background-image: radial-gradient(circle, $inner-color, $outer-color); + background-repeat: no-repeat; +} + +@mixin gradient-striped($color: rgba(255,255,255,.15), $angle: 45deg) { + background-image: -webkit-linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); + background-image: -o-linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); + background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_grid-framework.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_grid-framework.scss new file mode 100644 index 0000000000..4106eec479 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_grid-framework.scss @@ -0,0 +1,78 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `$grid-columns`. + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin make-grid-columns($i: 1, $list: ".col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}") { + @for $i from (1 + 1) through $grid-columns { + $list: "#{$list}, .col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}"; + } + #{$list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: ceil(($grid-gutter-width / 2)); + padding-right: floor(($grid-gutter-width / 2)); + } +} + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin float-grid-columns($class, $i: 1, $list: ".col-#{$class}-#{$i}") { + @for $i from (1 + 1) through $grid-columns { + $list: "#{$list}, .col-#{$class}-#{$i}"; + } + #{$list} { + float: left; + } +} + +@mixin calc-grid-column($index, $class, $type) { + @if ($type == width) and ($index > 0) { + .col-#{$class}-#{$index} { + width: percentage(($index / $grid-columns)); + } + } + @if ($type == push) and ($index > 0) { + .col-#{$class}-push-#{$index} { + left: percentage(($index / $grid-columns)); + } + } + @if ($type == push) and ($index == 0) { + .col-#{$class}-push-0 { + left: auto; + } + } + @if ($type == pull) and ($index > 0) { + .col-#{$class}-pull-#{$index} { + right: percentage(($index / $grid-columns)); + } + } + @if ($type == pull) and ($index == 0) { + .col-#{$class}-pull-0 { + right: auto; + } + } + @if ($type == offset) { + .col-#{$class}-offset-#{$index} { + margin-left: percentage(($index / $grid-columns)); + } + } +} + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin loop-grid-columns($columns, $class, $type) { + @for $i from 0 through $columns { + @include calc-grid-column($i, $class, $type); + } +} + +// Create grid for specific class +@mixin make-grid($class) { + @include float-grid-columns($class); + @include loop-grid-columns($grid-columns, $class, width); + @include loop-grid-columns($grid-columns, $class, pull); + @include loop-grid-columns($grid-columns, $class, push); + @include loop-grid-columns($grid-columns, $class, offset); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_grid.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_grid.scss new file mode 100644 index 0000000000..f91b1e9951 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_grid.scss @@ -0,0 +1,134 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +// Centered container element +@mixin container-fixed($gutter: $grid-gutter-width) { + margin-right: auto; + margin-left: auto; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + @include clearfix; +} + +// Creates a wrapper for a series of columns +@mixin make-row($gutter: $grid-gutter-width) { + margin-left: ceil(($gutter / -2)); + margin-right: floor(($gutter / -2)); + @include clearfix; +} + +// Generate the extra small columns +@mixin make-xs-column($columns, $gutter: $grid-gutter-width) { + position: relative; + float: left; + width: percentage(($columns / $grid-columns)); + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); +} + +@mixin make-xs-column-offset($columns) { + margin-left: percentage(($columns / $grid-columns)); +} + +@mixin make-xs-column-push($columns) { + left: percentage(($columns / $grid-columns)); +} + +@mixin make-xs-column-pull($columns) { + right: percentage(($columns / $grid-columns)); +} + +// Generate the small columns +@mixin make-sm-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-sm-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} + +@mixin make-sm-column-offset($columns) { + @media (min-width: $screen-sm-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-sm-column-push($columns) { + @media (min-width: $screen-sm-min) { + left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-sm-column-pull($columns) { + @media (min-width: $screen-sm-min) { + right: percentage(($columns / $grid-columns)); + } +} + +// Generate the medium columns +@mixin make-md-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-md-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} + +@mixin make-md-column-offset($columns) { + @media (min-width: $screen-md-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-md-column-push($columns) { + @media (min-width: $screen-md-min) { + left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-md-column-pull($columns) { + @media (min-width: $screen-md-min) { + right: percentage(($columns / $grid-columns)); + } +} + +// Generate the large columns +@mixin make-lg-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-lg-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} + +@mixin make-lg-column-offset($columns) { + @media (min-width: $screen-lg-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-lg-column-push($columns) { + @media (min-width: $screen-lg-min) { + left: percentage(($columns / $grid-columns)); + } +} + +@mixin make-lg-column-pull($columns) { + @media (min-width: $screen-lg-min) { + right: percentage(($columns / $grid-columns)); + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_hide-text.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_hide-text.scss new file mode 100644 index 0000000000..cd17cba636 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_hide-text.scss @@ -0,0 +1,21 @@ +// CSS image replacement +// +// Heads up! v3 launched with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (will be removed in v4) +@mixin hide-text() { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +// New mixin to use as of v3.0.1 +@mixin text-hide() { + @include hide-text; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_image.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_image.scss new file mode 100644 index 0000000000..193e30043c --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_image.scss @@ -0,0 +1,30 @@ +// Image Mixins +// - Responsive image +// - Retina image + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. +@mixin img-responsive($display: block) { + display: $display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + +// Retina image +// +// Short retina mixin for setting background-image and -size. Note that the +// spelling of `min--moz-device-pixel-ratio` is intentional. +@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-1x}"), "#{$file-1x}")); + + @media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (min--moz-device-pixel-ratio: 2), + only screen and (-o-min-device-pixel-ratio: 2/1), + only screen and (min-device-pixel-ratio: 2), + only screen and (min-resolution: 192dpi), + only screen and (min-resolution: 2dppx) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-2x}"), "#{$file-2x}")); + background-size: $width-1x $height-1x; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_labels.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_labels.scss new file mode 100644 index 0000000000..3fa443c662 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_labels.scss @@ -0,0 +1,12 @@ +// Labels + +@mixin label-variant($color) { + background-color: $color; + + &[href] { + &:hover, + &:focus { + background-color: darken($color, 10%); + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_list-group.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_list-group.scss new file mode 100644 index 0000000000..97985dd26a --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_list-group.scss @@ -0,0 +1,32 @@ +// List Groups + +@mixin list-group-item-variant($state, $background, $color) { + .list-group-item-#{$state} { + color: $color; + background-color: $background; + + // [converter] extracted a&, button& to a.list-group-item-#{$state}, button.list-group-item-#{$state} + } + + a.list-group-item-#{$state}, + button.list-group-item-#{$state} { + color: $color; + + .list-group-item-heading { + color: inherit; + } + + &:hover, + &:focus { + color: $color; + background-color: darken($background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: $color; + border-color: $color; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_nav-divider.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_nav-divider.scss new file mode 100644 index 0000000000..2e6da02a47 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_nav-divider.scss @@ -0,0 +1,10 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +@mixin nav-divider($color: #e5e5e5) { + height: 1px; + margin: (($line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: $color; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_nav-vertical-align.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_nav-vertical-align.scss new file mode 100644 index 0000000000..c8fbf1a7d6 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_nav-vertical-align.scss @@ -0,0 +1,9 @@ +// Navbar vertical align +// +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. + +@mixin navbar-vertical-align($element-height) { + margin-top: (($navbar-height - $element-height) / 2); + margin-bottom: (($navbar-height - $element-height) / 2); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_opacity.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_opacity.scss new file mode 100644 index 0000000000..88e9a576ab --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_opacity.scss @@ -0,0 +1,8 @@ +// Opacity + +@mixin opacity($opacity) { + opacity: $opacity; + // IE8 filter + $opacity-ie: ($opacity * 100); + filter: alpha(opacity=$opacity-ie); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_pagination.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_pagination.scss new file mode 100644 index 0000000000..da68a61566 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_pagination.scss @@ -0,0 +1,24 @@ +// Pagination + +@mixin pagination-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + > li { + > a, + > span { + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + } + &:first-child { + > a, + > span { + @include border-left-radius($border-radius); + } + } + &:last-child { + > a, + > span { + @include border-right-radius($border-radius); + } + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_panels.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_panels.scss new file mode 100644 index 0000000000..acbc7e5875 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_panels.scss @@ -0,0 +1,24 @@ +// Panels + +@mixin panel-variant($border, $heading-text-color, $heading-bg-color, $heading-border) { + border-color: $border; + + & > .panel-heading { + color: $heading-text-color; + background-color: $heading-bg-color; + border-color: $heading-border; + + + .panel-collapse > .panel-body { + border-top-color: $border; + } + .badge { + color: $heading-bg-color; + background-color: $heading-text-color; + } + } + & > .panel-footer { + + .panel-collapse > .panel-body { + border-bottom-color: $border; + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_progress-bar.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_progress-bar.scss new file mode 100644 index 0000000000..710682a1a6 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_progress-bar.scss @@ -0,0 +1,10 @@ +// Progress bars + +@mixin progress-bar-variant($color) { + background-color: $color; + + // Deprecated parent class requirement as of v3.2.0 + .progress-striped & { + @include gradient-striped; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_reset-filter.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_reset-filter.scss new file mode 100644 index 0000000000..bf73051200 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_reset-filter.scss @@ -0,0 +1,8 @@ +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. + +@mixin reset-filter() { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_reset-text.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_reset-text.scss new file mode 100644 index 0000000000..c9c28417fa --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_reset-text.scss @@ -0,0 +1,18 @@ +@mixin reset-text() { + font-family: $font-family-base; + // We deliberately do NOT reset font-size. + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: $line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_resize.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_resize.scss new file mode 100644 index 0000000000..83fa637917 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_resize.scss @@ -0,0 +1,6 @@ +// Resize anything + +@mixin resizable($direction) { + resize: $direction; // Options: horizontal, vertical, both + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_responsive-visibility.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_responsive-visibility.scss new file mode 100644 index 0000000000..1a819c5635 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_responsive-visibility.scss @@ -0,0 +1,27 @@ +// Responsive utilities + +// +// More easily include all the states for responsive-utilities.less. +// [converter] $parent hack +@mixin responsive-visibility($parent) { + #{$parent} { + display: block !important; + } + table#{$parent} { + display: table !important; + } + tr#{$parent} { + display: table-row !important; + } + th#{$parent}, + td#{$parent} { + display: table-cell !important; + } +} + +// [converter] $parent hack +@mixin responsive-invisibility($parent) { + #{$parent} { + display: none !important; + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_size.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_size.scss new file mode 100644 index 0000000000..abbe2463ce --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_size.scss @@ -0,0 +1,10 @@ +// Sizing shortcuts + +@mixin size($width, $height) { + width: $width; + height: $height; +} + +@mixin square($size) { + @include size($size, $size); +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_tab-focus.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_tab-focus.scss new file mode 100644 index 0000000000..7df0ae7ca1 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_tab-focus.scss @@ -0,0 +1,9 @@ +// WebKit-style focus + +@mixin tab-focus() { + // Default + outline: thin dotted; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_table-row.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_table-row.scss new file mode 100644 index 0000000000..2e4cd461a6 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_table-row.scss @@ -0,0 +1,28 @@ +// Tables + +@mixin table-row-variant($state, $background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.#{$state}, + > th.#{$state}, + &.#{$state} > td, + &.#{$state} > th { + background-color: $background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.#{$state}:hover, + > th.#{$state}:hover, + &.#{$state}:hover > td, + &:hover > .#{$state}, + &.#{$state}:hover > th { + background-color: darken($background, 5%); + } + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_text-emphasis.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_text-emphasis.scss new file mode 100644 index 0000000000..7e70e7a283 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_text-emphasis.scss @@ -0,0 +1,12 @@ +// Typography + +// [converter] $parent hack +@mixin text-emphasis-variant($parent, $color) { + #{$parent} { + color: $color; + } + a#{$parent}:hover, + a#{$parent}:focus { + color: darken($color, 10%); + } +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_text-overflow.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_text-overflow.scss new file mode 100644 index 0000000000..1593b25ea5 --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_text-overflow.scss @@ -0,0 +1,8 @@ +// Text overflow +// Requires inline-block or block for proper styling + +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/openecomp-ui/resources/scss/bootstrap/mixins/_vendor-prefixes.scss b/openecomp-ui/resources/scss/bootstrap/mixins/_vendor-prefixes.scss new file mode 100644 index 0000000000..3e4d61645a --- /dev/null +++ b/openecomp-ui/resources/scss/bootstrap/mixins/_vendor-prefixes.scss @@ -0,0 +1,247 @@ +// Vendor Prefixes +// +// All vendor mixins are deprecated as of v3.2.0 due to the introduction of +// Autoprefixer in our Gruntfile. They will be removed in v4. + +// - Animations +// - Backface visibility +// - Box shadow +// - Box sizing +// - Content columns +// - Hyphens +// - Placeholder text +// - Transformations +// - Transitions +// - User Select + +// Animations +@mixin animation($animation) { + -webkit-animation: $animation; + -o-animation: $animation; + animation: $animation; +} + +@mixin animation-name($name) { + -webkit-animation-name: $name; + animation-name: $name; +} + +@mixin animation-duration($duration) { + -webkit-animation-duration: $duration; + animation-duration: $duration; +} + +@mixin animation-timing-function($timing-function) { + -webkit-animation-timing-function: $timing-function; + animation-timing-function: $timing-function; +} + +@mixin animation-delay($delay) { + -webkit-animation-delay: $delay; + animation-delay: $delay; +} + +@mixin animation-iteration-count($iteration-count) { + -webkit-animation-iteration-count: $iteration-count; + animation-iteration-count: $iteration-count; +} + +@mixin animation-direction($direction) { + -webkit-animation-direction: $direction; + animation-direction: $direction; +} + +@mixin animation-fill-mode($fill-mode) { + -webkit-animation-fill-mode: $fill-mode; + animation-fill-mode: $fill-mode; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` + +@mixin backface-visibility($visibility) { + -webkit-backface-visibility: $visibility; + -moz-backface-visibility: $visibility; + backface-visibility: $visibility; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support it. + +@mixin box-shadow($shadow...) { + -webkit-box-shadow: $shadow; // iOS <4.3 & Android <4.1 + box-shadow: $shadow; +} + +// Box sizing +@mixin box-sizing($boxmodel) { + -webkit-box-sizing: $boxmodel; + -moz-box-sizing: $boxmodel; + box-sizing: $boxmodel; +} + +// CSS3 Content Columns +@mixin content-columns($column-count, $column-gap: $grid-gutter-width) { + -webkit-column-count: $column-count; + -moz-column-count: $column-count; + column-count: $column-count; + -webkit-column-gap: $column-gap; + -moz-column-gap: $column-gap; + column-gap: $column-gap; +} + +// Optional hyphenation +@mixin hyphens($mode: auto) { + word-wrap: break-word; + -webkit-hyphens: $mode; + -moz-hyphens: $mode; + -ms-hyphens: $mode; // IE10+ + -o-hyphens: $mode; + hyphens: $mode; +} + +// Placeholder text +@mixin placeholder($color: $input-color-placeholder) { + // Firefox + &::-moz-placeholder { + color: $color; + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &:-ms-input-placeholder { + color: $color; + } + // Internet Explorer 10+ + &::-webkit-input-placeholder { + color: $color; + } + // Safari and Chrome +} + +// Transformations +@mixin scale($ratio...) { + -webkit-transform: scale($ratio); + -ms-transform: scale($ratio); // IE9 only + -o-transform: scale($ratio); + transform: scale($ratio); +} + +@mixin scaleX($ratio) { + -webkit-transform: scaleX($ratio); + -ms-transform: scaleX($ratio); // IE9 only + -o-transform: scaleX($ratio); + transform: scaleX($ratio); +} + +@mixin scaleY($ratio) { + -webkit-transform: scaleY($ratio); + -ms-transform: scaleY($ratio); // IE9 only + -o-transform: scaleY($ratio); + transform: scaleY($ratio); +} + +@mixin skew($x, $y) { + -webkit-transform: skewX($x) skewY($y); + -ms-transform: skewX($x) skewY($y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + -o-transform: skewX($x) skewY($y); + transform: skewX($x) skewY($y); +} + +@mixin translate($x, $y) { + -webkit-transform: translate($x, $y); + -ms-transform: translate($x, $y); // IE9 only + -o-transform: translate($x, $y); + transform: translate($x, $y); +} + +@mixin translate3d($x, $y, $z) { + -webkit-transform: translate3d($x, $y, $z); + transform: translate3d($x, $y, $z); +} + +@mixin rotate($degrees) { + -webkit-transform: rotate($degrees); + -ms-transform: rotate($degrees); // IE9 only + -o-transform: rotate($degrees); + transform: rotate($degrees); +} + +@mixin rotateX($degrees) { + -webkit-transform: rotateX($degrees); + -ms-transform: rotateX($degrees); // IE9 only + -o-transform: rotateX($degrees); + transform: rotateX($degrees); +} + +@mixin rotateY($degrees) { + -webkit-transform: rotateY($degrees); + -ms-transform: rotateY($degrees); // IE9 only + -o-transform: rotateY($degrees); + transform: rotateY($degrees); +} + +@mixin perspective($perspective) { + -webkit-perspective: $perspective; + -moz-perspective: $perspective; + perspective: $perspective; +} + +@mixin perspective-origin($perspective) { + -webkit-perspective-origin: $perspective; + -moz-perspective-origin: $perspective; + perspective-origin: $perspective; +} + +@mixin transform-origin($origin) { + -webkit-transform-origin: $origin; + -moz-transform-origin: $origin; + -ms-transform-origin: $origin; // IE9 only + transform-origin: $origin; +} + +// Transitions + +@mixin transition($transition...) { + -webkit-transition: $transition; + -o-transition: $transition; + transition: $transition; +} + +@mixin transition-property($transition-property...) { + -webkit-transition-property: $transition-property; + transition-property: $transition-property; +} + +@mixin transition-delay($transition-delay) { + -webkit-transition-delay: $transition-delay; + transition-delay: $transition-delay; +} + +@mixin transition-duration($transition-duration...) { + -webkit-transition-duration: $transition-duration; + transition-duration: $transition-duration; +} + +@mixin transition-timing-function($timing-function) { + -webkit-transition-timing-function: $timing-function; + transition-timing-function: $timing-function; +} + +@mixin transition-transform($transition...) { + -webkit-transition: -webkit-transform $transition; + -moz-transition: -moz-transform $transition; + -o-transition: -o-transform $transition; + transition: transform $transition; +} + +// User select +// For selecting text on the page + +@mixin user-select($select) { + -webkit-user-select: $select; + -moz-user-select: $select; + -ms-user-select: $select; // IE10+ + user-select: $select; +} diff --git a/openecomp-ui/resources/scss/common/_base.scss b/openecomp-ui/resources/scss/common/_base.scss new file mode 100644 index 0000000000..aa3ffdeb2b --- /dev/null +++ b/openecomp-ui/resources/scss/common/_base.scss @@ -0,0 +1,66 @@ +html { + font-size: 100%; + height: 100%; +} + +body { + /* scrollbar styling for Internet Explorer */ + scrollbar-face-color: $scroll-bar-color; + scrollbar-track-color: $scroll-bar-color; + height: 100%; + @extend %noselect; +} + +/* scrollbar styling for Google Chrome | Safari | Opera */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background-color: transparent; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; + background-color: $light-gray; + border-right: 2px solid $content-background-color; +} + +/* Mozilla Firefox currently doesn't support scrollbar styling */ + +ul { + list-style: none; +} + +h1, h2, h3, h4, h5, h6, ul { + margin: 0; + padding: 0; +} + +input { + + padding: 7px 10px; +} + +fieldset { + border: none; +} + +fieldset { + label { + display: inline-block; + } +} + +.nav-tabs > li > a:focus, +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: none; +} + +.box-hover { + border: 1px solid $light-blue; +} diff --git a/openecomp-ui/resources/scss/common/_layout.scss b/openecomp-ui/resources/scss/common/_layout.scss new file mode 100644 index 0000000000..6d63a79f55 --- /dev/null +++ b/openecomp-ui/resources/scss/common/_layout.scss @@ -0,0 +1,22 @@ +.sdc-app { + height: 100%; + +} +.flex { + display: flex; + flex: 1; +} + +.flex-column { + @extend .flex; + flex-direction: column; +} + +.content-area { + padding: 30px 60px 70px 60px; + overflow-y: auto; + height: 100%; + &.no-padding-content-area { + padding: 0; + } +} diff --git a/openecomp-ui/resources/scss/common/_typography.scss b/openecomp-ui/resources/scss/common/_typography.scss new file mode 100644 index 0000000000..46edfb03de --- /dev/null +++ b/openecomp-ui/resources/scss/common/_typography.scss @@ -0,0 +1,115 @@ +/* Fonts */ +@font-face { + font-family: Omnes-Light; + src: url('../fonts/omnes-att-light.otf'); +} + +@font-face { + font-family: Omnes-Regular; + src: url('../fonts/omnes-att-regular.otf'); +} + +@font-face { + font-family: Omnes-Medium; + src: url('../fonts/omnes-att-medium.otf'); +} + +@font-face { + font-family: Omnes-Bold; + src: url('../fonts/omnes-att-bold.otf'); +} + +$base-font-regular: omnes-regular, "Omnes-Regular"; +$base-font-light: omnes-light, "Omnes-Light"; +$base-font-medium: omnes-medium, "Omnes-Medium"; +$base-font-bold: omnes-bold, "Omnes-Bold"; + +$heading-font-1: 36px; +$heading-font-2: 24px; +$heading-font-3: 20px; +$heading-font-4: 18px; +$heading-font-5: 16px; + +$body-font-1: 14px; +$body-font-2: 13px; +$body-font-3: 12px; + +$icon-font-size: 10px; +$icon-font-family: FontAwesome; +$radio-font-family: Arial; + +.heading-1 { + font-family: $base-font-light; + font-size: $heading-font-1; +} + +.heading-2 { + font-family: $base-font-light; + font-size: $heading-font-2; +} + +.heading-3-light { + font-family: $base-font-light; + font-size: $heading-font-3; +} + +.heading-3 { + font-family: $base-font-regular; + font-size: $heading-font-3; +} + +.heading-3-medium { + font-family: $base-font-medium; + font-size: $heading-font-3; +} + +.heading-4 { + font-family: $base-font-regular; + font-size: $heading-font-4; +} + +.heading-4-medium { + font-family: $base-font-medium; + font-size: $heading-font-4; +} + +.heading-5 { + font-family: $base-font-regular; + font-size: $heading-font-5; +} + +.heading-5-medium { + font-family: $base-font-medium; + font-size: $heading-font-5; +} + +.body-1 { + font-family: $base-font-regular; + font-size: $body-font-1; +} + +.body-1-medium { + font-family: $base-font-medium; + font-size: $body-font-1; +} + +.body-2 { + font-family: $base-font-regular; + font-size: $body-font-2; +} + +.body-2-medium { + font-family: $base-font-medium; + font-size: $body-font-2; +} + +.body-3 { + font-family: $base-font-regular; + font-size: $body-font-3; +} + +.body-3-medium { + font-family: $base-font-medium; + font-size: $body-font-3; +} + diff --git a/openecomp-ui/resources/scss/common/_utils.scss b/openecomp-ui/resources/scss/common/_utils.scss new file mode 100644 index 0000000000..70f3416e4b --- /dev/null +++ b/openecomp-ui/resources/scss/common/_utils.scss @@ -0,0 +1,299 @@ + + +/* Prefix */ + +$box-sizing-prefix: webkit moz spec; +$border-radius-prefix: webkit spec; +$box-shadow-radius-prefix: webkit moz spec; +$text-shadow-radius-prefix: spec; +$text-shadow-prefix: spec; +$box-shadow-prefix: all; +$linear-gradient-prefix: all; +$transition-prefix: webkit moz o spec; +$flex-prefix: webkit spec; +$browserPrefixes: webkit moz o ms; + +@mixin prefix($property, $value, $prefixeslist: 'all') { + @if $prefixeslist == all { + -webkit-#{$property}: $value; + -moz-#{$property}: $value; + -ms-#{$property}: $value; + -o-#{$property}: $value; + #{$property}: $value; + } @else { + @each $prefix in $prefixeslist { + @if $prefix == webkit { + -webkit-#{$property}: $value; + } @else if $prefix == moz { + -moz-#{$property}: $value; + } @else if $prefix == ms { + -ms-#{$property}: $value; + } @else if $prefix == o { + -o-#{$property}: $value; + } @else if $prefix == spec { + #{$property}: $value; + } @else { + @warn "No such prefix: #{$prefix}"; + } + } + } +} + +/* Value Prefix*/ +@mixin value-suffix-with-range($property, $valuesuffix, $from, $to, $prefixeslist) { + + @if $prefixeslist == all { + #{property} : -webkit-#{$valuesuffix}($from, $to); + #{property} : -moz-#{$valuesuffix}($from, $to); + #{property} : -o-#{$valuesuffix}($from, $to); + #{property} : -ms-#{$valuesuffix}($from, $to); + + } @else { + @each $prefix in $prefixeslist { + @if $prefix == webkit { + #{property} : -webkit-#{$valuesuffix}($from, $to); + } @else if $prefix == moz { + #{property} : -moz-#{$valuesuffix}($from, $to); + } @else if $prefix == ms { + #{property} : -ms-#{$valuesuffix}($from, $to); + } @else if $prefix == o { + #{property} : -o-#{$valuesuffix}($from, $to); + } @else { + @warn "No such prefix: #{$prefix}"; + } + } + } +} + +/* Box sizing */ +@mixin box-sizing($value: border-box) { + @include prefix(box-sizing, $value, $box-sizing-prefix); +} + +/* Borders & Shadows */ +@mixin box-shadow($value) { + @include prefix(box-shadow, $value, $box-shadow-radius-prefix); +} + +@mixin text-shadow($value) { + @include prefix(text-shadow, $value, $text-shadow-radius-prefix); +} + +@mixin border-radius($value, $positions: all) { + @if ($positions == all) { + @include prefix(border-radius, $value, $border-radius-prefix); + } @else { + @each $position in $positions { + @include prefix(border-#{$position}-radius, $value, $border-radius-prefix); + } + } + +} + +@mixin transition($value) { + @include prefix(transition, $value, $transition-prefix); +} + +/* Opacity */ +@mixin opacity($alpha) { + $ie-opacity: round($alpha * 100); + opacity: $alpha; + filter: unquote("alpha(opacity = #{$ie-opacity})"); +} + +/* Ellipsis */ +@mixin ellipsis() { + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + white-space: nowrap; + display: inline-block; +} + +@mixin gradient($from, $to) { + /* fallback/image non-cover color */ + background-color: $from; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($from), to($to)); + @include value-suffix-with-range(background-color, linear-gradient, $from, $to, $linear-gradient-prefix); +} + +/* Vertical placement of multuple lines of text */ +@mixin vertical-text($height) { + position: absolute; + top: 50%; + margin-top: -$height/2; +} + +@mixin multiline-ellipsis($height, $lineheight, $ellipsiswidth: 3em) { + + $ellipsiswidth: 3em !default; + + .ellipsis { + overflow: hidden; + height: $height; + line-height: $lineheight; + } + + .ellipsis:before { + content: ""; + float: left; + width: 5px; + height: $height; + } + + .ellipsis > *:first-child { + float: right; + width: 100%; + margin-left: -5px; + } + + .ellipsis:after { + content: "\02026"; + + float: right; + position: relative; + top: -25px; + left: 100%; + width: $ellipsiswidth; + margin-left: -$ellipsiswidth; + padding-right: 5px; + + text-align: right; + } + +} + +@mixin text-vertical-align($align: middle) { + display: table; + width: 100%; + + & > * { + vertical-align: $align; + display: table-cell; + } +} + +@mixin center-element($width) { + width: $width; + margin-left: auto; + margin-right: auto; +} + +@mixin center-content($width) { + & > * { + @include center-element($width); + } +} + +/* transform-rotate */ +// @mixin +// Defines a 2D rotation, the angle is specified in the parameter +// @param +// $deg - angle in degrees +@mixin transform-rotate($deg) { + transform: rotate($deg + deg); /* IE10 and Mozilla */ + -ms-transform: rotate($deg + deg); /* IE 9 */ + -webkit-transform: rotate($deg + deg); /* Safari and Chrome */ +} + +/* transform-translate */ +// @mixin +// Defines a 2D rotation, the angle is specified in the parameter +// @param +// $deg - angle in degrees +@mixin transform-translate($x, $y) { + transform: translate($x, $y); /* IE10 and Mozilla */ + -ms-transform: translate($x, $y); /* IE 9 */ + -webkit-transform: translate($x, $y); /* Safari and Chrome */ +} + +/* transform-scale */ +// @mixin +// Defines a 2D scale transformation, changing the elements width and height +// @param +// $width - width +// @param +// $height - height +@mixin transform-scale($width, $height) { + transform: scale($width, $height); /* IE10 and Mozilla */ + -ms-transform: scale($width, $height); /* IE 9 */ + -webkit-transform: scale($width, $height); /* Safari and Chrome */ +} + +@mixin scrollable() { + ::-webkit-scrollbar { + width: 8px; + } +} + +/**/ +@mixin keyframe-animation($animationType, $properties, $fromValue, $toValue) { + + @keyframes #{$animationType} { + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-moz-keyframes #{$animationType}{ + /* Firefox */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-webkit-keyframes #{$animationType} { + /* Safari and Chrome */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-o-keyframes #{$animationType} { + /* Opera */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } +} diff --git a/openecomp-ui/resources/scss/common/_variables.scss b/openecomp-ui/resources/scss/common/_variables.scss new file mode 100644 index 0000000000..92c20cdfdf --- /dev/null +++ b/openecomp-ui/resources/scss/common/_variables.scss @@ -0,0 +1,47 @@ + +// primary colors +$blue: #009fdb; +$dark-blue: #0568ae; +$light-blue: #71c5e8; +$green: #4ca90c; +$dark-green: #007a3e; +$light-green: #b5bd00; +$orange: #ea7400; +$yellow: #ffb81c; +$dark-purple: #702f8a; +$purple: #9063cd; +$light-purple: #caa2dd; +$black: #000000; +$dark-gray: #5a5a5a; +$gray: #959595; +$light-gray: #d2d2d2; +$white: #ffffff; + +// Secondary Colors +$red: #cf2a2a; + +$background-alice-blue: #e5f5fb; +$background-gray: #f2f2f2; +$text-black: #191919; +$link-blue: #056bae; +$functional-green: #007a3e; +$functional-yellow: #ffb81c; +$tlv-gray: #f8f8f8; +$tlv-light-gray: #eaeaea; +$tlv-hover: #e6f6fb; + +$content-background-color: $white; + +$scroll-bar-color: $text-black;//$light-gray; + +//responsive @media params +$tablet-max-width: 1024px; +$laptop-min-width: 1224px; +$desktop-min-width: 1824px; + +/* Textures */ +$images-folder-name: "../images"; +$plus-circle-icon: $images-folder-name + "/plus-circle-icon.svg"; +$interface-icon: $images-folder-name + "/interface.svg"; +$sdc-logo: $images-folder-name + "/logo.svg"; +$warning-icon: $images-folder-name + "/warning.svg"; diff --git a/openecomp-ui/resources/scss/components/_buttons.scss b/openecomp-ui/resources/scss/components/_buttons.scss new file mode 100644 index 0000000000..b39ea495ab --- /dev/null +++ b/openecomp-ui/resources/scss/components/_buttons.scss @@ -0,0 +1,46 @@ + +$plus-circle-icon-size: 18px; +.plus-icon-button { + background: url($plus-circle-icon) no-repeat; + background-size: $plus-circle-icon-size; + width: $plus-circle-icon-size; + height: $plus-circle-icon-size; + cursor: pointer; + &.small { + background-size: $plus-circle-icon-size - 6; + width: $plus-circle-icon-size - 6; + height: $plus-circle-icon-size - 6; + } +} + +.primary-btn{ + border: 1px solid; + border-color: $blue; + color: $blue; + font-weight: bolder; + @extend .body-1; + text-align: center; + padding: 7px; + border-radius: 5px; + cursor: pointer; + align-self: center; + background-color: $white; + .primary-btn-text { + width: 100%; + } + &:hover { + color: $blue; + border-color: $blue; + background-color: $tlv-hover; + &:active { + color: $blue; + border-color: $blue; + } + } + + &:focus:not(:hover) { + color: $blue; + border-color: $blue; + background-color: $white; + } +} diff --git a/openecomp-ui/resources/scss/components/_dropzone.scss b/openecomp-ui/resources/scss/components/_dropzone.scss new file mode 100644 index 0000000000..cad752d415 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_dropzone.scss @@ -0,0 +1,8 @@ + +.active-dragging { + border: 3px dashed $dark-blue; + border-radius: 20px; + .draggable-wrapper { + opacity: 0.5; + } +} diff --git a/openecomp-ui/resources/scss/components/_dualListBox.scss b/openecomp-ui/resources/scss/components/_dualListBox.scss new file mode 100644 index 0000000000..543c1c8b92 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_dualListBox.scss @@ -0,0 +1,46 @@ +.dual-list-box { + display: flex; + margin: 25px 0 10px 0; + + .dual-search-multi-select-section { + $multi-select-box-width: 398px; + width: $multi-select-box-width; + .dual-text-box-search-title, .dual-list-box-multi-select-text { + @extend .body-1; + color: $dark-gray; + } + .dual-text-box-search { + margin: 5px 0 30px 0; + } + .dual-list-box-multi-select { + flex: 1 1; + display: flex; + height: 166px; + margin-bottom: 0; + select { + width: 100%; + margin: 0; + padding: 0; + overflow-y: scroll; + height: inherit; + option { + padding: 4px 0 4px 10px; + } + } + } + } + .dual-list-options-bar { + margin: 62px 54px 27px 54px; + padding-top: 23px; + .dual-list-option { + text-align: center; + line-height: 10px; + font-size: 25px; + width: 20px; + height: 15px; + cursor: pointer; + margin-top: 20px; + color: $blue; + } + } +} diff --git a/openecomp-ui/resources/scss/components/_expandableInput.scss b/openecomp-ui/resources/scss/components/_expandableInput.scss new file mode 100644 index 0000000000..52f410a61b --- /dev/null +++ b/openecomp-ui/resources/scss/components/_expandableInput.scss @@ -0,0 +1,61 @@ +$transitionLength: 0.5s; + +@mixin expand-transition($tl){ + transition: width $tl, background-color $tl, cursor $tl; +} + +@mixin color-transition($tl){ + transition: color $tl; +} + +.expandable-input-wrapper { + display: flex; + &:hover{ + .form-control { + border-color: $gray; + } + } + .expandable-input-control { + flex: 1 1; + margin: 0; + .form-control { + border-radius: 20px; + } + align-self: center; + } + .expandable-active { + @include expand-transition($transitionLength); + width: 215px; + cursor: text; + } + .expandable-not-active { + @include expand-transition($transitionLength); + width: 28px; + cursor: pointer; + background-color: transparent; + color: transparent; + } + + .expandable-icon { + @include color-transition($transitionLength); + position: relative; + left: -20px; + align-self: center; + width: 0; + cursor: pointer; + color: $dark-gray; + } + + .expandable-close-button{ + @extend .expandable-icon; + color: $gray; + opacity: 0.5; + &:hover { + opacity: 1; + } + } + .expandable-icon-active { + @include color-transition($transitionLength); + color: $blue; + } +} diff --git a/openecomp-ui/resources/scss/components/_forms.scss b/openecomp-ui/resources/scss/components/_forms.scss new file mode 100644 index 0000000000..3c50fe694a --- /dev/null +++ b/openecomp-ui/resources/scss/components/_forms.scss @@ -0,0 +1,45 @@ +.section-title { + @extend .heading-3-medium; + padding: 50px 0 30px 0; + &:first-child { + padding: 0 0 30px 0; + } +} +.dropdown-multi-select { + .Select { + display: block; + width: 100%; + .Select-control { + height: 28px; + border-radius: 2px; + .Select-input { + height: 28px; + input { + height: 28px; + padding: 0; + } + } + .Select-placeholder { + line-height: 30px; + } + } + &.Select--multi { + .Select-value { + color: $text-black; + background-color: transparent; + border-color: $light-gray; + margin-top: 2px; + margin-left: 2px; + border-radius: 1px; + } + .Select-value-icon { + border-right-color: $light-gray; + } + .Select-arrow-zone { + padding-top: 4px; + } + } + } +} + + diff --git a/openecomp-ui/resources/scss/components/_inputOptions.scss b/openecomp-ui/resources/scss/components/_inputOptions.scss new file mode 100644 index 0000000000..107751b07b --- /dev/null +++ b/openecomp-ui/resources/scss/components/_inputOptions.scss @@ -0,0 +1,39 @@ +.input-options { + display: flex; + border: 1px solid $light-gray; + border-radius: 2px; + height: 30px; + &:hover { + border-color: $gray; + } + .input-options-select { + float: left; + border: none; + transition-property: width; + transition-duration: 300ms; + padding-top:0px; + padding-bottom: 0px; + height:28px; + } + + .input-options-other{ + float: left; + height: 30px; + border: none; + padding-top:0px; + padding-bottom: 0px; + height:28px; + } + .input-options-separator { + width: 1px; + height: 24px; + margin-top: 2px; + margin-bottom: 2px; + border:1px solid $light-gray; + } +} + +.input-options.has-error { + border-color: #A94442; +} + diff --git a/openecomp-ui/resources/scss/components/_listEditorView.scss b/openecomp-ui/resources/scss/components/_listEditorView.scss new file mode 100644 index 0000000000..704faaf098 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_listEditorView.scss @@ -0,0 +1,157 @@ +.list-editor-view { + @extend .flex-column; + background-color: $content-background-color; + + .list-editor-view-title { + @extend .section-title; + } + + .list-editor-view-title-inline { + @extend .list-editor-view-title; + position: relative; + top: 9px; + &:first-child { + padding: 0; + } + } + + .list-editor-view-add-section { + display: inline-block; + padding: 0 10px 0 10px; + } + + .list-editor-view-actions { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid $light-gray; + padding-bottom: 18px; + .list-editor-view-add-controller { + @extend .body-1-medium; + color: $blue; + display: table; + cursor: pointer; + position: relative; + top: 9px; + span { + display: table-cell; + vertical-align: middle; + &:nth-child(2) { + padding-left: 10px; + } + } + } + + .search-wrapper { + width: 265px; + } + } + + .list-editor-view-list-scroller { + @extend .flex; + overflow: auto; + margin: 18px 0 30px 0; + } + + .list-editor-view-list { + @extend .flex-column; + + + .list-editor-item-view { + margin: 8px 0; + border: 1px solid $light-gray; + background-color: $white; + cursor: pointer; + display: flex; + height: 100px; + min-height: 100px; + overflow: hidden; + .list-editor-item-view-content { + padding: 10px 28px; + display: flex; + flex-basis: 95%; + width: 95%; + .list-editor-item-view-field { + flex: 1 1; + color: $black; + padding: 0; + @include ellipsis; + overflow-wrap: break-word; + white-space: initial; + .number-field { + align-content: center; + } + .title { + @extend .body-1; + padding-bottom: 5px; + &:not(:first-child) { + line-height: 0.9; + } + } + .text { + @extend .body-1; + } + } + } + .list-editor-item-view-controller { + display: flex; + flex-basis: 5%; + align-self: center; + justify-content: center; + flex-direction: column; + .fa { + transition: color .3s; + font-size: 22px; + color: $white; + &:first-child{ + margin-bottom: 10px; + } + } + } + &:hover { + @extend .box-hover; + .list-editor-item-view-controller { + .fa { + color: $gray; + } + } + } + } + } + + &.thinner-list { + background-color: $white; + padding: 0; + margin-top: 35px; + + .list-editor-view-list { + border-top: 0; + padding-top: 0; + margin-top: 23px; + .list-editor-item-view { + &:hover { + border-color: $light-gray; + } + margin: 5px 0 0 0; + height: 30px; + border-top: 0; + border-left: 0; + border-right: 0; + .list-editor-item-view-content { + padding: 4px; + .name { + @extend .body-2-medium; + flex-basis: 36%; + } + } + .list-editor-item-view-controller { + .fa-trash-o { + font-size: 20px; + } + } + } + } + } +} + + diff --git a/openecomp-ui/resources/scss/components/_loader.scss b/openecomp-ui/resources/scss/components/_loader.scss new file mode 100644 index 0000000000..ddff9af6e3 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_loader.scss @@ -0,0 +1,159 @@ +.onboarding-loader { + .onboarding-loader-backdrop { + top: 0; + right: 0; + bottom: 0; + left: 0; + position: absolute; + background-color: #E1E4E6; + opacity: 0.5; + } + .tlv-loader { + height: 63px; + width: 63px; + position: absolute; + top: 30%; + left: 50%; + margin-top: -10.5px; + margin-left: -10.5px; + } + .tlv-loader.large { + transform: scale(1); + } + .tlv-loader::before { + background-color: $gray; + border-radius: 50%; + box-shadow: 21px 21px 0px 0px $gray, 0px 42px 0px 0px $gray, -21px 21px 0px 0px $gray; + content: ''; + display: block; + height: 21px; + width: 21px; + position: absolute; + left: 50%; + margin-left: -10.5px; + } + .tlv-loader::after { + border-radius: 50%; + content: ''; + display: block; + position: absolute; + height: 21px; + width: 21px; + animation: dot-move-2 4.5s infinite ease-in; + } + @keyframes dot-move { + 0% { + background-color: $blue; + left: 21px; + top: 0; + } + 25% { + background-color: $orange; + left: 42px; + top: 21px; + } + 50% { + background-color: $light-purple; + left: 21px; + top: 42px; + } + 75% { + background-color: $light-green; + left: 0; + top: 21px; + } + 100% { + background-color: $blue; + left: 21px; + top: 0; + } + } + @keyframes dot-move-2 { + 0% { + background-color: $blue; + left: 21px; + top: 0; + } + 6.25% { + background-color: $blue; + left: 42px; + top: 21px; + } + 12.5% { + background-color: $blue; + left: 21px; + top: 42px; + } + 18.75% { + background-color: $blue; + left: 0; + top: 21px; + } + 25% { + background-color: $orange; + left: 21px; + top: 0; + } + 31.25% { + background-color: $orange; + left: 42px; + top: 21px; + } + 37.5% { + background-color: $orange; + left: 21px; + top: 42px; + } + 43.75% { + background-color: $orange; + left: 0; + top: 21px; + } + 50% { + background-color: $light-purple; + left: 21px; + top: 0; + } + 56.25% { + background-color: $light-purple; + left: 42px; + top: 21px; + } + 62.5% { + background-color: $light-purple; + left: 21px; + top: 42px; + } + 68.75% { + background-color: $light-purple; + left: 0; + top: 21px; + } + 75% { + background-color: $light-green; + left: 21px; + top: 0; + } + 81.25% { + background-color: $light-green; + left: 42px; + top: 21px; + } + 87.5% { + background-color: $light-green; + left: 21px; + top: 42px; + } + 93.75% { + background-color: $light-green; + left: 0; + top: 21px; + } + 100% { + background-color: $blue; + left: 21px; + top: 0; + } + } +} + diff --git a/openecomp-ui/resources/scss/components/_navigationSideBar.scss b/openecomp-ui/resources/scss/components/_navigationSideBar.scss new file mode 100644 index 0000000000..404f43ca68 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_navigationSideBar.scss @@ -0,0 +1,62 @@ +.software-product-navigation-side-bar { + width: 245px; + height: 100%; + background-color: $white; + border-right: 1px solid $light-gray; + box-shadow: 1px -1px 3px 0px $tlv-light-gray; + border-bottom: 0; + + .navigation-side-content { + overflow: hidden; + height: 100%; + + .navigation-group { + height: 100%; + display: flex; + flex-direction: column; + background-color: $tlv-gray; + .group-name { + @extend .heading-5-medium; + @include ellipsis; + min-height: 56px; + display: block; + padding: 18px 12px 16px 40px; + background-color: $white; + border-bottom: 1px solid $tlv-light-gray; + } + } + + .navigation-group-items { + padding-left: 20px; + overflow-y: auto; + + .navigation-group-item { + @extend .body-1; + cursor: pointer; + margin: 18px 0; + padding-left: 20px; + color: $dark-gray; + line-height: 17px; + &.selected-item { + padding-left: 0; + .collapse.in { + padding-left: 20px; + } + } + .navigation-group-item-name { + @include ellipsis; + white-space: normal; + &.selected { + @extend .body-1-medium; + border-left: 4px solid $blue; + padding-left: 18px; + color: $blue; + } + &.bold-name { + @extend .body-1-medium; + } + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/components/_notifications.scss b/openecomp-ui/resources/scss/components/_notifications.scss new file mode 100644 index 0000000000..426f05cd89 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_notifications.scss @@ -0,0 +1,29 @@ + +.notification-modal { + + .modal-content { + .modal-header { + padding: 15px 10px 10px; + .modal-title { + @extend .heading-5-medium; + } + } + .modal-body { + padding: 30px 15px; + @extend .body-1-medium; + } + } + + &.danger { + .modal-content .modal-header { + border-top: 3px solid $red; + } + } + + &.warning { + .modal-content .modal-header { + border-top: 3px solid $yellow; + } + } + +} diff --git a/openecomp-ui/resources/scss/components/_progressBar.scss b/openecomp-ui/resources/scss/components/_progressBar.scss new file mode 100644 index 0000000000..040c8cefd7 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_progressBar.scss @@ -0,0 +1,24 @@ + .progress-bar-view { + display: -webkit-box; + padding: 5px; + .progress-bar-outside { + display: flex; + width: 90%; + justify-content: space-between; + background-color: lightgray; + border-radius: 15px; + height: 10px; + .progress-bar-inside { + display: block; + border: 1px solid gainsboro; + background-color: #4faa39; + border-radius: inherit; + } + } + .progress-bar-view-label { + margin-right: -30px; + margin-top: -2px; + margin-left: 10px; + color: black; + } + } diff --git a/openecomp-ui/resources/scss/components/_punchOut.scss b/openecomp-ui/resources/scss/components/_punchOut.scss new file mode 100644 index 0000000000..65ba24d884 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_punchOut.scss @@ -0,0 +1,12 @@ +&.dox-ui-punch-out { + background-color: $content-background-color; +} + +&.dox-ui-punch-out.dox-ui-punch-out-full-page { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-y: auto; +} diff --git a/openecomp-ui/resources/scss/components/_sequenceDiagram.scss b/openecomp-ui/resources/scss/components/_sequenceDiagram.scss new file mode 100644 index 0000000000..5fad92a668 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_sequenceDiagram.scss @@ -0,0 +1,11 @@ +.sequence-diagram { + @extend .flex-column; + .sequence-diagram-sequencer { + flex: 0 1 auto; + margin-bottom: 30px; + } + .sequence-diagram-action-buttons { + flex: 0.1; + text-align: center; + } +} diff --git a/openecomp-ui/resources/scss/components/_slidePanel.scss b/openecomp-ui/resources/scss/components/_slidePanel.scss new file mode 100644 index 0000000000..61c9a189f0 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_slidePanel.scss @@ -0,0 +1,35 @@ + +.slide-panel { + transition: left .5s,right .5s; + + .slide-panel-header { + padding: 10px; + height: 45px; + display: table; + width: 100%; + .slide-panel-header-title, .collapse-double-icon { + display: table-cell; + vertical-align: middle; + } + .slide-panel-header-title { + @extend .body-2; + text-align: center; + width: 100%; + color: $text-black; + } + .collapse-double-icon { + color: $text-black; + cursor: pointer; + } + } + + .slide-panel-content { + opacity: 1; + transition: opacity .5s, visibility .5s; + + &.closed { + opacity: 0; + visibility: hidden; + } + } +} diff --git a/openecomp-ui/resources/scss/components/_submitErrorResponse.scss b/openecomp-ui/resources/scss/components/_submitErrorResponse.scss new file mode 100644 index 0000000000..fdac5ebe45 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_submitErrorResponse.scss @@ -0,0 +1,23 @@ + +.submit-error-response-view { + max-height: 500px; + overflow: auto; + .list-group-item { + border-top: none; + } + .panel-title { + a { + &:after { + + content: "(details)"; + color: $link-blue; + float: right; + + } + &:hover { + color: $blue; + text-decoration: underline; + } + } + } +} diff --git a/openecomp-ui/resources/scss/components/_toggleInput.scss b/openecomp-ui/resources/scss/components/_toggleInput.scss new file mode 100644 index 0000000000..fcf0902adf --- /dev/null +++ b/openecomp-ui/resources/scss/components/_toggleInput.scss @@ -0,0 +1,62 @@ +.toggle-input-wrapper { + $toggle-width: 40px; + $toggle-height: 20px; + display: table; + .toggle-switch, .toggle-input-label { + display: table-cell; + vertical-align: middle; + padding-left: 10px; + } + .toggle { + position: absolute; + margin-left: -9999px; + visibility: hidden; + } + .toggle + label { + @extend %noselect; + display: block; + position: relative; + cursor: pointer; + outline: none; + } + + input.toggle-round-flat + label { + padding: 1px; + width: $toggle-width; + height: $toggle-height; + background-color: $dark-gray; + border-radius: $toggle-height; + transition: background 0.4s; + } + input.toggle-round-flat + label:before, + input.toggle-round-flat + label:after { + display: block; + position: absolute; + content: ""; + } + input.toggle-round-flat + label:before { + top: 1px; + left: 1px; + bottom: 1px; + right: 1px; + background-color: $white; + border-radius: $toggle-height; + transition: background 0.4s; + } + input.toggle-round-flat + label:after { + top: 4px; + left: 4px; + bottom: 4px; + width: $toggle-height - 8; + background-color: $dark-gray; + border-radius: $toggle-height - 8; + transition: margin 0.4s, background 0.4s; + } + input.toggle-round-flat:checked + label { + background-color: $link-blue; + } + input.toggle-round-flat:checked + label:after { + margin-left: $toggle-height; + background-color: $link-blue; + } +} diff --git a/openecomp-ui/resources/scss/components/_validationForm.scss b/openecomp-ui/resources/scss/components/_validationForm.scss new file mode 100644 index 0000000000..93444c2e88 --- /dev/null +++ b/openecomp-ui/resources/scss/components/_validationForm.scss @@ -0,0 +1,102 @@ +form { + .validation-form-content { + .validation-input-wrapper { + position: relative; + flex: 1; + } + .nav-tabs { + .invalid-tab:not(.active) { + a { + color: $red; + } + } + } + .validation-error-message { + &.bottom { + .tooltip-arrow { + border-bottom-color: $red; + } + } + &.right { + .tooltip-arrow { + border-right-color: $red; + } + } + &.left { + .tooltip-arrow { + border-left-color: $red; + } + } + .tooltip-inner { + background-color: $red; + } + } + .input-row { + padding-bottom: 32px; + &:only-child { + padding-bottom: 0; + } + &:last-child { + padding-bottom: 0; + } + .form-group { + margin-bottom: 0; + } + } + + .rows-section { + .row-flex-components { + display: flex; + } + .validation-input-wrapper { + flex: 1; + } + .empty-col { + flex: 1.2; + content: ' '; + } + .empty-two-col { + flex: 2.4; + content: ' '; + } + + .two-col { + flex: 2.2; + } + .three-col { + flex: 3.4; + } + .single-col { + flex: 1.2; + display: flex; + &:after { + flex: 0.2; + content: ' '; + } + &.add-line-break { + .control-label { + &:after { + content: "\00a0"; + display: block; + } + } + } + } + } + } + + .validation-buttons { + padding: 20px 0; + text-align: right; + button:first-child { + margin-right: 15px; + } + } +} + +.modal-body { + .validation-buttons { + padding: 20px 15px; + background-color: $tlv-gray; + } +} diff --git a/openecomp-ui/resources/scss/components/_versionController.scss b/openecomp-ui/resources/scss/components/_versionController.scss new file mode 100644 index 0000000000..3511470bfb --- /dev/null +++ b/openecomp-ui/resources/scss/components/_versionController.scss @@ -0,0 +1,109 @@ +.version-controller-bar { + .navbar-inverse { + @extend .body-1-medium; + background-color: transparent; + border-bottom: $tlv-light-gray thin solid; + padding-top: 14px; + padding-bottom: 12px; + margin-bottom: 0; + .container { + width: 100%; + padding: 0 52px; + height: 30px; + .navbar-collapse { + padding-left: 0; + .items-in-left { + display: flex; + height: 30px; + .version-section { + .form-group { + margin-right: 5px; + .input-options { + border: none; + .input-options-select { + padding-top: 4px; + } + } + } + } + .vc-status { + display: flex; + padding-left: 14px; + border-left: $light-gray thin solid; + .status-text { + align-self: center; + display: flex; + padding-left: 3px; + .status-text-dash { + padding: 0 9px; + } + } + .onboarding-status-icon { + width: 25px; + height: 19px; + background-image: url('../images/ecomp/ASDC_Sprite.png'); + background-position: -306px 674px; + align-self: center; + } + .checkout-status-icon { + display: flex; + & > .catalog-tile-check-in-status.sprite-new.checkout-editable-status-icon { + width: 25px; + height: 19px; + align-self: center; + margin-left: 5px; + } + } + } + } + .items-in-right { + display: flex; + height: 30px; + .action-buttons { + display: flex; + border-right: $gray thin solid; + padding: 0 13px; + .version-control-buttons { + display: flex; + } + .vc-nav-item-button { + border: 1px solid $light-gray; + border-radius: 5px; + cursor: pointer; + margin-right:10px; + padding: 6px 20px; + &:hover, &:focus { + background-color: lightgrey; + } + &.button-submit{ + background-color: transparent; + color: $green; + border-color: $green; + } + &.button-checkin-checkout { + background-color: $white; + @extend .body-1; + } + } + .revert-btn, .save-btn { + height: 24px; + width: 24px; + align-self: center; + cursor: pointer; + margin-right: 10px; + } + } + .vc-nav-item-close { + display: flex; + padding-left: 18px; + padding-top: 3px; + align-self: center; + @extend .body-1; + color: $gray; + cursor: pointer; + } + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_entitlementPools.scss b/openecomp-ui/resources/scss/modules/_entitlementPools.scss new file mode 100644 index 0000000000..df7cea4cd8 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_entitlementPools.scss @@ -0,0 +1,65 @@ + +.entitlement-pools-list-editor { + + .list-editor-view-list { + .list-editor-item-view { + min-height: 110px; + height: 110px; + } + .list-editor-item-view-field { + + .entitlement-pools-count, .entitlement-parameters, .contract-number, .type{ + color: $purple; + } + .entitlement-parameters { + @include ellipsis; + margin-bottom: 2px; + } + .entitlement-pools-count { + @extend .heading-1; + margin-top: -10px; + + } + } + } +} + +.entitlement-pools-modal { + .validation-form-content { + padding: 50px; + } + .modal-body { + padding: 0; + } + .entitlement-pools-form { + .tab-content { + padding: 50px; + } + .entitlement-pools-form-row { + + display: flex; + justify-content: space-between; + & > * { + flex: 0 1 47%; + } + .validation-input-wrapper { + .form-group { + textarea { + height: 184px; + } + .entitlement-pools-form-row-threshold-value { + margin-top: 23px; + margin-left: 10px; + width: 189px; + } + .dropdown-multi-select .Select { + z-index: 1080; + } + } + } + } + } + .validation-buttons { + padding: 20px 50px; + } +} diff --git a/openecomp-ui/resources/scss/modules/_featureGroup.scss b/openecomp-ui/resources/scss/modules/_featureGroup.scss new file mode 100644 index 0000000000..f66a01c290 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_featureGroup.scss @@ -0,0 +1,77 @@ +.feature-groups-list-editor { + .list-editor-view-list { + .list-editor-item-view { + min-height: 110px; + height: 110px; + } + .list-editor-item-view-field { + .feature-groups-count-field { + display: inline-block; + &:first-child { + margin-right: 95px; + } + } + .feature-groups-count-ep { + @extend .heading-1; + color: $light-blue; + } + .feature-groups-count-lk { + @extend .heading-1; + color: $light-green; + } + } + } +} + +.feature-group-modal { + .modal-body { + padding: 0; + } + .feature-group-form { + .button-tab { + @extend .body-1-medium; + color: $dark-gray; + padding: 6px; + border: 0; + background-color: $white; + box-shadow: none; + &:first-child { + margin-right: 28px; + } + &.active, &:hover { + color: $text-black; + border-bottom: 2px solid $blue; + } + } + .no-items-msg { + margin-top: 55px; + color: $dark-gray; + } + .tab-content { + padding: 50px; + .field-section { + @extend .body-2-medium; + margin-bottom: 23px; + width: 400px; + color: $black; + } + .description-field { + height: 170px; + } + .list-editor-item-view-content { + white-space: nowrap; + overflow: hidden; + > div { + overflow: hidden; + text-overflow: ellipsis; + &:not(:last-of-type) { + margin-right: 24px; + } + } + } + } + .validation-buttons { + padding: 20px 50px; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_licenseAgreement.scss b/openecomp-ui/resources/scss/modules/_licenseAgreement.scss new file mode 100644 index 0000000000..a7afd01cc5 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_licenseAgreement.scss @@ -0,0 +1,92 @@ + +.license-agreement-list-editor { + + .list-editor-view-list { + .list-editor-item-view { + min-height: 110px; + height: 110px; + } + .list-editor-item-view-field { + .list-editor-item-view-field-tight { + vertical-align: top; + display: inline-block; + &:first-child { + @include ellipsis; + margin-right: 95px; + width: 20%; + overflow-wrap: break-word; + } + } + .feature-groups-count, .contract-number, .type { + color: $light-green; + } + .feature-groups-count { + @extend .heading-1; + padding-top: 2px; + text-align: center; + } + .contract-number { + margin-bottom: 8px; + } + } + } +} + +.license-agreement-modal { + .modal-body { + padding: 0; + } + .license-agreement-form { + .button-tab{ + @extend .body-1-medium; + color: $dark-gray; + padding: 6px; + border: 0; + background-color: $white; + box-shadow: none; + &:first-child { + margin-right: 28px; + } + &.active, &:hover { + color: $text-black; + border-bottom: 2px solid $blue; + } + } + .no-items-msg { + margin-top: 55px; + color: $dark-gray; + } + .tab-content { + padding: 50px; + .list-editor-item-view-content { + white-space: nowrap; + overflow: hidden; + > div { + overflow: hidden; + text-overflow: ellipsis; + &:not(:last-of-type) { + margin-right: 24px; + } + } + } + } + .license-agreement-form-row { + display: flex; + justify-content: space-between; + .license-agreement-form-col { + flex: 0 1 45%; + } + .validation-input-wrapper { + flex: 0 1 45%; + .form-group { + textarea { + height: 100px; + } + } + } + } + .validation-buttons { + padding: 20px 50px; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_licenseKeyGroup.scss b/openecomp-ui/resources/scss/modules/_licenseKeyGroup.scss new file mode 100644 index 0000000000..19b4792949 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_licenseKeyGroup.scss @@ -0,0 +1,54 @@ +.license-key-groups-list-editor { + .list-editor-view-list { + .list-editor-item-view { + min-height: 110px; + height: 110px; + } + .list-editor-item-view-field { + + .operational-scope, .type { + color: $orange; + } + .operational-scope { + margin-bottom: 0px; + @include ellipsis; + } + } + } +} + +.license-key-groups-modal { + .modal-body { + padding: 0; + } + .license-key-groups-form { + .validation-form-content { + padding: 50px; + .field-section { + @extend .body-2-medium; + margin-bottom: 23px; + width: 400px; + color: $black; + } + .license-key-groups-form-row { + display: flex; + justify-content: space-between; + .options-input-form-row { + width: 370px; + } + .validation-input-wrapper { + flex: 0 1 45%; + .form-group { + textarea { + height: 100px; + } + + } + } + } + } + .validation-buttons { + padding: 20px 50px; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_licenseModel.scss b/openecomp-ui/resources/scss/modules/_licenseModel.scss new file mode 100644 index 0000000000..6912e19bcd --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_licenseModel.scss @@ -0,0 +1,20 @@ +.license-model-type-modal { + .modal-body { + padding: 0; + } + .validation-form-content { + padding: 50px; + .field-section { + @extend .body-2-medium; + margin-bottom: 23px; + width: 400px; + color: $black; + } + textarea { + height: 107px; + } + } + .validation-buttons { + padding: 20px 50px; + } +} diff --git a/openecomp-ui/resources/scss/modules/_onboardingCatalog.scss b/openecomp-ui/resources/scss/modules/_onboardingCatalog.scss new file mode 100644 index 0000000000..6020866d4a --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_onboardingCatalog.scss @@ -0,0 +1,164 @@ +$transitionLength: 0.6s; + +.catalog-view { + background-color: $background-gray; + height: 100%; + overflow: hidden; + height: 100%; + .catalog-header { + padding-top: 20px; + margin: 0 48px 0 20px; + border-bottom: 1px solid $light-gray; + display: flex; + flex-direction: row; + justify-content: space-between; + .catalog-header-title { + @extend .heading-2; + margin: 0 0 10px 0; + } + .expandable-input-wrapper { + margin: 0 0 10px 0; + } + } + .catalog-list { + max-height: 100%; + overflow: auto; + display: flex; + flex-wrap: wrap; + padding: 40px 10px 0 10px; + &:after { + content: ' '; + display:block; + width: 100%; + height: 85px; + } + .tile { + background-color: $white; + margin: 10px; + width: 214px; + height: 210px; + display: flex; + flex-direction: column; + + } + .create-catalog-item { + border: 2px dashed $light-gray; + background-color: transparent; + &:hover { + .plus-section { + display: none; + } + .primary-btn { + display: inherit; + } + } + justify-content: center; + .plus-section { + align-self: center; + display: flex; + flex-direction: column; + .plus-icon-button { + align-self: center; + background-size: 23px; + width: 23px; + height: 23px; + margin-bottom: 4px; + } + } + .primary-btn { + display: none; + width: 176px; + &.new-license-model { + margin-bottom: 18px; + } + &:hover { + background-color: $background-alice-blue; + } + } + } + .catalog-tile { + cursor: pointer; + border: 1px solid $tlv-light-gray; + &:hover { + @include box-shadow(3px 3px 5px 0 rgba(24,24,25,.04)); + } + &:active { + border: 1px solid $light-blue; + } + .catalog-tile-type { + padding-top: 7px; + border-radius: 50%; + width: 40px; + height: 40px; + background: white; + border: 4px solid #F2F2F2; + position: relative; + top: -12px; + left: -7px; + &:before { + @extend .body-1; + color: $light-blue; + } + &.license-model-type { + padding-left: 13px; + &:before { + content: "L" + } + } + &.software-product-type { + padding-left: 8px; + &:before { + content: "SP" + } + } + } + .catalog-tile-icon { + text-align: center; + flex-basis: 60%; + justify-content: center; + background-size: 60px 60px; + background-repeat: no-repeat; + background-position: center; + display: flex; + .icon { + align-self: center; + height: 65px; + width: 65px; + background-repeat: no-repeat; + margin-bottom: 27px; + &.license-model-type-icon { + background-image: url('../images/onboarding/vendor-license-model.svg'); + } + &.software-product-type-icon { + background-image: url('../images/onboarding/vendor-software-product.svg'); + } + } + } + .catalog-tile-content { + border-top: 1px solid $background-gray; + flex-basis: 30%; + padding: 8px; + display: flex; + justify-content: space-between; + .catalog-tile-item-details { + overflow: hidden; + } + .catalog-tile-item-name { + @extend .body-1-medium; + @include ellipsis(); + width: 150px; + color: $dark-gray + } + .catalog-tile-item-version { + @extend .body-1; + color: $gray; + } + .catalog-tile-check-in-status { + width: 25px; + height: 19px; + align-self: center; + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductAttachmentPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductAttachmentPage.scss new file mode 100644 index 0000000000..657bb544a7 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductAttachmentPage.scss @@ -0,0 +1,153 @@ +.software-product-attachments { + @extend .body-1; + width: 100%; + height: 100%; + display: flex; + + .software-product-attachments-tree { + display: flex; + border-right: 1px solid $light-gray; + margin: 0px; + padding: 5px 3px 10px 3px; + overflow: auto; + height: 100%; + + .tree-wrapper { + flex: 1 1; + position: relative; + padding-bottom: 10px; + + .tree-block-inside { + padding-left: 17px; + padding-top: 8px; + padding-bottom: 1px; + .tree-node-row { + cursor: default; + white-space: nowrap; + &.tree-node-selected::before { + position: absolute; + left: 0; + right: 0; + height: 20px; + display: inline-block; + content: ' '; + background-color: $light-gray; + } + + &.tree-node-clicked::before { + position: absolute; + left: 4px; + right: 4px; + height: 20px; + display: inline-block; + content: ' '; + font-weight: bold; + background-color: $white; + } + + .tree-node-expander { + position: relative; + display: inline-block; + cursor: pointer; + .fa { + position: absolute; + left: -7px; + top: -11px; + @include transition(transform 0.3s); + } + &.tree-node-expander-collapsed .fa { + @include transform-rotate(-90); + } + } + + .tree-node-icon { + margin: 0 7px; + color: $text-black; + opacity: .5; + .tree-node-validation-error::after { + content: "!"; + font-weight: bold; + position: absolute; + color: $red; + font-size: large; + left: -7px; + bottom: -5px; + } + } + + .error-status { + color: $red; + &.error-status-selected { + font-weight: bold; + } + &.error-status-hovered { + font-weight: bold; + background-color: $blue; + } + } + + .tree-element-text { + @extend %noselect; + position: relative; + padding-right: 5px; + &.error-status-selected { + font-weight: bold; + } + } + } + + } + } + } + .software-product-attachments-separator { + border: 1px solid $tlv-light-gray; + height: 100%; + width: 5px; + cursor: e-resize; + } + + .software-product-attachments-error-list { + text-align: center; + margin-top: 12px; + display: flex; + align-content: flex-start; + flex-direction: column; + padding-left: 70px; + padding-right: 50px; + overflow: auto; + margin-bottom: 70px; + .error-item { + &.selected { + background-color: $light-gray; + } + &.clicked { + color: $blue; + } + &.shifted { + margin-top: 20px; + } + text-align: left; + .error-item-file-name { + font-weight: bold; + } + .error-item-file-type { + margin-right: 5px; + &.strong { + font-weight: bold; + } + &.ERROR { + color: $red; + } + &.WARNING { + color: $yellow; + } + } + } + + .error-item:hover { + cursor: default; + background-color: $tlv-hover; + } + } + +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductComponentGeneral.scss b/openecomp-ui/resources/scss/modules/_softwareProductComponentGeneral.scss new file mode 100644 index 0000000000..4ff2d2c14a --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductComponentGeneral.scss @@ -0,0 +1,10 @@ +.vsp-components-general { + .general-data { + .one-line-textarea { + height: 30px; + } + } + .additional-validation-form { + margin-top: 50px; + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductComponentNetwork.scss b/openecomp-ui/resources/scss/modules/_softwareProductComponentNetwork.scss new file mode 100644 index 0000000000..6097f3ef52 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductComponentNetwork.scss @@ -0,0 +1,46 @@ +.vsp-components-network { + .network-data { + .network-data-title { + @extend .body-2-medium; + padding-bottom: 20px; + padding-left: 15px; + } + .single-col { + .validation-input-wrapper { + label { + max-width: 230px; + } + } + } + } + .list-editor-view { + margin-top: 50px; + } +} +.network-nic-modal { + .modal-body { + padding: 0; + } + .vsp-components-network-editor { + .editor-data { + padding-left: 50px; + padding-right: 50px; + padding-top: 20px; + height: 500px; + overflow-y: auto; + .part-title { + @extend .heading-5; + padding-bottom: 10px; + padding-left: 14px; + } + .part-title-small { + @extend .heading-3; + padding-bottom: 10px; + padding-left: 14px; + } + .network-radio label { + font-size: 15px; + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductCreatePage.scss b/openecomp-ui/resources/scss/modules/_softwareProductCreatePage.scss new file mode 100644 index 0000000000..deac736cfa --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductCreatePage.scss @@ -0,0 +1,39 @@ +.software-product-type-modal { + .modal-dialog { + @extend .modal-lg !optional; + .modal-body { + padding: 0; + } + .validation-form-content { + padding: 50px; + .software-product-form-row { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + .software-product-inline-section { + padding: 0 20px; + flex: 45%; + .validation-input-wrapper { + .field-section { + @extend .body-2-medium; + margin-bottom: 23px; + color: $black; + } + textarea { + height: 191px; + } + select optgroup[label] { + color: $dark-blue; + } + option { + color: black; + } + } + } + } + } + .validation-buttons { + padding: 20px 50px; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss new file mode 100644 index 0000000000..e40bb38ea9 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss @@ -0,0 +1,229 @@ +.upload-modal-body-content { + padding-left: 30px; + padding-right: 30px; + padding-bottom: 10px; + .title { + @extend .body-1-medium; + } + .file-name { + padding-left: 5px; + @extend .body-1-medium; + } +} +.software-product-view { + display: flex; + height:100%; + + .description { + @extend .body-1; + overflow: hidden; + padding-right: 20px; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + .name { + @extend .body-1-medium; + } + .software-product-landing-view-right-side { + @extend .flex; + overflow-y: hidden; + .processes-page-title { + padding-top: 38px; + padding-left: 53px; + padding-bottom: 20px; + } + .list-editor-view { + .list-editor-view-title { + margin-bottom: 0; + } + } + } + .software-product-landing-view { + transition: border .2s; + padding-bottom: 50px; + + + .list-editor-view { + padding-top: 50px; + padding-left: 0; + padding-right: 0; + } + .software-product-landing-view-top { + .details-container { + @extend .flex-column; + .single-detail-section { + @extend .flex-column; + &.title-section { + flex: 0.8; + @extend .heading-5-medium; + } + } + .multiple-details-section { + @extend .flex; + justify-content: space-between; + .detail-col { + .title { + &.extra-large { + min-width: 130px; + } + } + } + } + } + .row { + margin: 0; + display: flex; + .details-panel { + flex: 1; + margin-right: 50px; + &:last-child { + margin-right: 0; + } + } + .col-md-6 { + padding: 0; + + overflow-wrap: break-word; + &:first-child { + padding-right: 25px; + } + &:last-child { + padding-left: 25px; + } + } + .title { + @extend .body-1-medium; + } + .software-product-landing-view-heading-title { + @extend .section-title; + color: $dark-gray; + padding-bottom: 20px; + &:first-child { + padding-bottom: 20px; + } + } + .software-product-landing-view-top-block { + cursor: pointer; + border: 1px solid $light-gray; + padding: 28px 28px; + height: 250px; + display: flex; + justify-content: space-between; + background-color: $white; + &:hover { + @extend .box-hover; + } + .col-md-6 { + @extend .body-1; + } + .software-product-landing-view-top-block-col { + @extend .body-1; + flex: 0.8; + display: flex; + justify-content: space-between; + flex-direction: column; + .description { + overflow: hidden; + padding-right: 20px; + } + .attachment-details { + padding-bottom: 10px; + } + .attachment-details-count { + color: $light-blue; + } + } + .software-product-landing-view-top-block-col-upl { + @extend .flex; + text-align: center; + flex-direction: column; + justify-content: center; + border: 2px dashed $light-gray; + @extend .body-1; + align-items: center; + .upload-btn { + padding: 15px 55px; + + } + .drag-text { + color: $blue; + font-weight: bolder; + } + .or-text { + margin-top: 10px; + margin-bottom: 10px; + } + } + } + } + } + } +} + +.vsp-details-page { + .vsp-general-tab { + .validation-form-content { + margin: 0; + } + .validation-buttons { + margin: 43px 0; + padding: 0 52px; + } + .section-title { + padding: 50px 0 30px 0; + &.general { + padding-top: 0; + } + } + .validation-form-content { + .vsp-general-tab-inline-section { + display: flex; + &.coupling-items { + justify-content: flex-start; + .validation-input-wrapper:not(:last-child) { + margin-right: 40px; + } + } + .vsp-general-tab-sub-section:not(:last-of-type) { + margin-right: 40px; + } + .field-section { + width: 440px; + } + .form-group textarea { + height: 192px; + } + select optgroup[label] { + color: $dark-blue; + } + option { + color: $dark-gray; + } + .Select, .input-options { + width: 440px; + } + } + + .vsp-general-tab-section { + &.licenses { + >.vsp-general-tab-inline-section { + .validation-input-wrapper:first-child { + margin-right: 40px; + } + } + } + } + } + .validation-buttons { + position: fixed; + display: block; + bottom: 0; + width: 66%; + } + .validation-input-wrapper { + flex: none; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareProductNetworksPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductNetworksPage.scss new file mode 100644 index 0000000000..780f348374 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductNetworksPage.scss @@ -0,0 +1,24 @@ +.vsp-networks { + .wrapper { + display: flex; + height: 100%; + .left-side{ + height:100% + } + .right-side { + width:100%; + .network-data { + padding-left: 60px; + padding-right: 60px; + padding-top: 18px; + .network-data-title { + @extend .body-2-medium; + padding-bottom: 20px; + padding-left: 15px; + } + } + + } + } +} + diff --git a/openecomp-ui/resources/scss/modules/_softwareProductProcessesPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductProcessesPage.scss new file mode 100644 index 0000000000..167dad92e2 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareProductProcessesPage.scss @@ -0,0 +1,84 @@ +.edit-process-modal { + background-color: $white; + height: 100%; + &.modal-body { + padding: 0; + background-color: $white; + } + .vsp-processes-editor { + padding-left: 0; + padding-right: 0; + .editor-title { + @extend .heading-2; + color: $dark-gray; + padding-bottom: 50px; + } + .file-upload-box { + @extend .body-1; + display: flex; + text-align: center; + flex-direction: column; + justify-content: center; + border: 2px dashed $light-gray; + padding-top: 20px; + padding-bottom: 20px; + + align-items: center; + .upload-btn { + padding: 20px; + padding-top: 7px; + padding-bottom: 3px; + } + .drag-text { + color: $blue; + font-weight: bolder; + } + .or-text { + margin-top: 10px; + margin-bottom: 10px; + } + } + .vsp-processes-editor-data { + padding: 28px 54px; + transition: border .2s; + .vsp-process-dropzone-view { + background-color: transparent; + padding: 15px; + &.active-dragging { + border: 3px dashed $dark-blue; + border-radius: 20px; + .draggable-wrapper { + opacity: 0.5; + } + } + } + .validation-input-wrapper { + .form-group { + .vsp-process-description { + height: 200px; + } + } + } + } + } +} + +.vsp-processes-page { + .processes-list { + @extend .flex-column; + } + .list-editor-view { + .list-editor-view-list { + .list-editor-item-view { + .list-editor-item-view-content { + .list-editor-item-view-field { + .artifact-name { + @extend .body-1; + color: $light-green; + } + } + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_softwareproductComponentLoadBalancing.scss b/openecomp-ui/resources/scss/modules/_softwareproductComponentLoadBalancing.scss new file mode 100644 index 0000000000..731ab89571 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_softwareproductComponentLoadBalancing.scss @@ -0,0 +1,43 @@ +.vsp-components-load-balancing { + .halb-data { + .load-balancing-page-title { + @extend .section-title; + &:first-child { + padding: 0 0 40px 0; + } + } + .question { + padding-top: 10px; + &:first-child { + padding-top: 0; + } + } + .title { + @extend .body-1-medium; + cursor: pointer; + margin-bottom: 8px; + .fa { + @include transition(transform 0.3s); + margin-right: 5px; + font-size: $icon-font-size; + position: relative; + top: -1px; + } + } + .row { + padding-left: 24px; + } + .col-md-9 { + padding-left: 8px; + } + .add-padding { + padding-bottom: 20px; + } + .new-line { + margin-left: 16px; + } + textarea.form-control { + height: 90px; + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_uploadScreen.scss b/openecomp-ui/resources/scss/modules/_uploadScreen.scss new file mode 100644 index 0000000000..4aa07f1580 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_uploadScreen.scss @@ -0,0 +1,46 @@ +.heat-validation-stand-alone { + .upload-screen { + margin-top: 100px; + .title { + text-align: center; + margin-bottom: 50px; + } + .upload-screen-upload-block { + text-align: center; + padding: 50px; + border: 2px dashed lightgray; + } + .upload-screen-drop-zone { + &.active-dragging { + border: 3px dashed $dark-blue; + border-radius: 20px; + .draggable-wrapper { + opacity: 0.5; + } + } + } + } + + .attachments-screen { + .back-button { + z-index: 1000; + position: absolute; + top: 20px; + right: 20px; + width: 200px; + } + .software-product-attachments { + display: block; + .software-product-view { + display: block; + .software-product-landing-view-right-side { + display: block; + .software-product-attachments-main { + display: flex; + height: 100vh; + } + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_vspComponentMonitoring.scss b/openecomp-ui/resources/scss/modules/_vspComponentMonitoring.scss new file mode 100644 index 0000000000..c49e4f551d --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_vspComponentMonitoring.scss @@ -0,0 +1,40 @@ + +.vsp-component-monitoring { + .snmp-dropzone { + .section-title { + padding-bottom: 20px; + } + &:first-child { + padding-bottom: 50px; + } + .software-product-landing-view-top-block-col-upl { + @extend .body-1; + width: 400px; + display: flex; + text-align: center; + flex-direction: column; + justify-content: center; + border: 2px dashed $light-gray; + padding: 25px 0; + align-items: center; + .upload-btn { + padding: 20px; + padding-top: 7px; + padding-bottom: 3px; + } + .drag-text { + color: $blue; + font-weight: bolder; + } + .or-text { + margin-top: 10px; + margin-bottom: 10px; + } + } + + } + + .delete-button { + min-width: 0; + } +} diff --git a/openecomp-ui/resources/scss/modules/_vspComponentQuestionnaire.scss b/openecomp-ui/resources/scss/modules/_vspComponentQuestionnaire.scss new file mode 100644 index 0000000000..aad628aac8 --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_vspComponentQuestionnaire.scss @@ -0,0 +1,54 @@ + +.vsp-component-questionnaire-view { + input[type='radio'], input[type='checkbox'] { + &:before { + border: 1px solid $dark-gray; + cursor: pointer; + } + &:checked:before { + border: 1px solid $blue; + } + } + .component-questionnaire-validation-form { + + + .section-sub-title { + @extend .heading-5; + padding-bottom: 10px; + } + .section-field { + textarea { + height: 80px; + } + } + + .rows-section { + .row-flex-components { + display: flex; + } + + .vertical-flex { + flex-direction: column; + .control-label { + @extend .body-2-medium; + } + .radio-options-content-row { + display: flex; + margin-top: -4px; + .validation-input-wrapper { + width: 240px; + margin-right: 7px; + + & > .form-group { + display: flex; + } + .form-group .radio { + width: auto; + margin-right: 0; + } + } + } + } + } + } +} diff --git a/openecomp-ui/resources/scss/modules/_workflows.scss b/openecomp-ui/resources/scss/modules/_workflows.scss new file mode 100644 index 0000000000..c1555df28c --- /dev/null +++ b/openecomp-ui/resources/scss/modules/_workflows.scss @@ -0,0 +1,43 @@ + +.workflows { + position: absolute; + bottom: 0; + right: 0; + left: 0; + top: 0; + + background-color: $background-gray; + .list-editor-view-list .list-editor-item-view .list-editor-item-view-content .list-editor-item-view-field:last-child { + flex: 2 1; + } + + .list-editor-view { + padding: 30px 50px; + } + +} + +.workflows-editor-modal { + .modal-body { + padding: 0; + } + .validation-form-content { + padding: 15px; + } +} + +.sequence-diagram { + position: absolute; + bottom: 0; + right: 0; + left: 0; + top: 0; + + padding-bottom: 20px; + .sequence-diagram-action-buttons { + display: flex; + button { + margin: 20px; + } + } +} diff --git a/openecomp-ui/resources/scss/onboarding.scss b/openecomp-ui/resources/scss/onboarding.scss new file mode 100644 index 0000000000..f146b3910b --- /dev/null +++ b/openecomp-ui/resources/scss/onboarding.scss @@ -0,0 +1,10 @@ +.dox-ui { + @import "bootstrap"; + + @import "../css/font-awesome.min.css"; + @import "~react-select/dist/react-select.min.css"; + + @import "common"; + @import "components"; + @import "modules"; +} diff --git a/openecomp-ui/resources/scss/style.scss b/openecomp-ui/resources/scss/style.scss new file mode 100644 index 0000000000..ac020219ad --- /dev/null +++ b/openecomp-ui/resources/scss/style.scss @@ -0,0 +1,3 @@ +@import "common"; +@import "components"; +@import "modules"; diff --git a/openecomp-ui/src/heat.html b/openecomp-ui/src/heat.html new file mode 100644 index 0000000000..3334074782 --- /dev/null +++ b/openecomp-ui/src/heat.html @@ -0,0 +1,12 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Heat Validation</title> +</head> + +<body> +<div id="heat-validation-app"></div> +</body> + +<script src="heat-validation_en.js"></script> +</html> diff --git a/openecomp-ui/src/index.html b/openecomp-ui/src/index.html new file mode 100644 index 0000000000..7f4fe04f06 --- /dev/null +++ b/openecomp-ui/src/index.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta http-equiv="Content-type" content="text/html"/> + <link rel="SHORTCUT ICON" href="images/icons/favicon.png"/> + <title>Service Design & Creation</title> +</head> +<body> +<div id="sdc-app" class="sdc-app"></div> + +<script> + +(function(){ + + /** + * Bundle Import script( By Language)! + */ + + var DEFAULT_LANG = 'en'; + var lang = localStorage.getItem('user_locale') || ((navigator && (navigator.language || navigator.userLanguage)) || DEFAULT_LANG).toLowerCase(); + + function writeAppBundle() { + var supportedLangs = [ + //<!--prod:supported-langs--><!--/prod:supported-langs--> + ]; + if(-1 === supportedLangs.indexOf(lang)) { + lang = DEFAULT_LANG; + } + + var bundleScript = document.createElement('script'); + bundleScript.src = 'bundle_' + lang + '.js'; + document.write(bundleScript.outerHTML); + } + + writeAppBundle(); + +})() +</script> + +</body> +</html> diff --git a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx new file mode 100644 index 0000000000..f2ec1582f3 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx @@ -0,0 +1,133 @@ +import React, {Component} from 'react'; +import ListGroupItem from 'react-bootstrap/lib/ListGroupItem.js'; +import ListGroup from 'react-bootstrap/lib/ListGroup.js'; +import Panel from 'react-bootstrap/lib/Panel.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +/** + * parsing and showing the following Java Response object + * + * public class ValidationResponse { + private boolean valid = true; + private Collection<ErrorCode> vspErrors; + private Collection<ErrorCode> licensingDataErrors; + private Map<String, List<ErrorMessage>> uploadDataErrors; + private Map<String, List<ErrorMessage>> compilationErrors; + private QuestionnaireValidationResult questionnaireValidationResult; + } + + * public class ErrorCode { + private String id; + private String message; + private ErrorCategory category; + } + + * public class ErrorMessage { + private final ErrorLevel level; + private final String message; + } + */ +class SubmitErrorResponse extends Component { + + + render() { + let {validationResponse} = this.props; + return ( + <div className='submit-error-response-view'> + {validationResponse.vspErrors && this.renderVspErrors(validationResponse.vspErrors)} + {validationResponse.licensingDataErrors && this.renderVspErrors(validationResponse.licensingDataErrors)} + {validationResponse.compilationErrors && this.renderCompilationErrors(validationResponse.compilationErrors)} + {validationResponse.uploadDataErrors && this.renderUploadDataErrors(validationResponse.uploadDataErrors)} + {validationResponse.questionnaireValidationResult && this.renderQuestionnaireValidationResult(validationResponse.questionnaireValidationResult)} + </div> + ); + } + + renderVspErrors(vspErrors) { + return ( + <Panel header={i18n('VSP Errors')} collapsible>{this.parseErrorCodeCollection(vspErrors)}</Panel> + ); + } + + renderLicensingDataErrors(licensingDataErrors) { + return ( + <Panel + header={i18n('Licensing Data Errors')} + collapsible>{this.parseErrorCodeCollection(licensingDataErrors)} + </Panel> + ); + } + + renderUploadDataErrors(uploadDataErrors) { + return ( + <Panel + header={i18n('Upload Data Errors')} + collapsible>{this.parseMapOfErrorMessagesList(uploadDataErrors)} + </Panel> + ); + } + + renderCompilationErrors(compilationErrors) { + return ( + <Panel + header={i18n('Compilation Errors')} + collapsible>{this.parseMapOfErrorMessagesList(compilationErrors)} + </Panel> + ); + } + + parseErrorCodeCollection(errors) { + return ( + <ListGroup>{errors.map(error => + <ListGroupItem className='error-code-list-item'> + <div><span>{i18n('Category: ')}</span>{error.category}</div> + <div><span>{i18n('Message: ')}</span>{error.message}</div> + </ListGroupItem> + )}</ListGroup> + ); + } + + parseMapOfErrorMessagesList(errorMap) { + return ( + <ListGroup> + {Object.keys(errorMap).map(errorStringKey => + <Panel header={errorStringKey} collapsible> + <ListGroup>{errorMap[errorStringKey].map(error => + <ListGroupItem className='error-code-list-item'> + <div><span>{i18n('Level: ')}</span>{error.level}</div> + <div><span>{i18n('Message: ')}</span>{error.message}</div> + </ListGroupItem> + )}</ListGroup> + </Panel> + )} + </ListGroup> + ); + } + + + renderQuestionnaireValidationResult(questionnaireValidationResult) { + if (!questionnaireValidationResult.valid) { + return this.parseAndRenderCompositionEntityValidationData(questionnaireValidationResult.validationData); + } + } + + parseAndRenderCompositionEntityValidationData(validationData) { + let {entityType, entityId, errors = [], subEntitiesValidationData = []} = validationData; + return ( + <ListGroup> + <Panel header={`${entityType}: ${entityId}`} collapsible> + <ListGroup>{errors.map(error => + <ListGroupItem className='error-code-list-item'> + <div>{error}</div> + </ListGroupItem> + )}</ListGroup> + {subEntitiesValidationData.map(subValidationData => this.parseAndRenderCompositionEntityValidationData(subValidationData))} + </Panel> + </ListGroup> + ); + } + + +} + +export default SubmitErrorResponse; diff --git a/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx b/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx new file mode 100644 index 0000000000..cc971c608c --- /dev/null +++ b/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import Button from 'react-bootstrap/lib/Button.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +let typeClass = { + 'default': 'primary', + error: 'danger', + warning: 'warning', + success: 'success' +}; + + +class ConfirmationModalView extends React.Component { + + static propTypes = { + show: React.PropTypes.bool, + type: React.PropTypes.oneOf(['default', 'error', 'warning', 'success']), + msg: React.PropTypes.node, + title: React.PropTypes.string, + confirmationDetails: React.PropTypes.object, + confirmationButtonText: React.PropTypes.string, + + }; + + static defaultProps = { + show: false, + type: 'warning', + title: 'Warning', + msg: '', + confirmationButtonText: i18n('Delete') + }; + + render() { + let {title, type, msg, show, confirmationButtonText} = this.props; + + return( + <Modal show={show} className={`notification-modal ${typeClass[type]}`}> + <Modal.Header> + <Modal.Title>{title}</Modal.Title> + </Modal.Header> + <Modal.Body>{msg}</Modal.Body> + <Modal.Footer> + <Button bsStyle={typeClass[type]} onClick={() => this.props.onDeclined(this.props.confirmationDetails)}>{i18n('Cancel')}</Button> + <Button bsStyle={typeClass[type]} onClick={() => this.props.onConfirmed(this.props.confirmationDetails)}>{confirmationButtonText}</Button> + </Modal.Footer> + </Modal> + ); + }; +} + +export default ConfirmationModalView; diff --git a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx new file mode 100644 index 0000000000..4a106b5ff4 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx @@ -0,0 +1,71 @@ +import React from 'react'; +import classnames from 'classnames'; + +import VersionController from 'nfvo-components/panel/versionController/VersionController.jsx'; +import NavigationSideBar from 'nfvo-components/panel/NavigationSideBar.jsx'; + +export default class TabulatedEditor extends React.Component { + + render() { + const {versionControllerProps, navigationBarProps, onToggle, onVersionSwitching, onCreate, onSave, onClose, onVersionControllerAction, onNavigate, children} = this.props; + const {className = ''} = React.Children.only(children).props; + const child = this.prepareChild(); + + return ( + <div className='software-product-view'> + <div className='software-product-navigation-side-bar'> + <NavigationSideBar {...navigationBarProps} onSelect={onNavigate} onToggle={onToggle}/> + </div> + <div className='software-product-landing-view-right-side flex-column'> + <VersionController + {...versionControllerProps} + onVersionSwitching={version => onVersionSwitching(version)} + callVCAction={onVersionControllerAction} + onCreate={onCreate && this.handleCreate} + onSave={onSave && this.handleSave} + onClose={() => onClose(versionControllerProps)}/> + <div className={classnames('content-area', `${className}`)}> + { + child + } + </div> + </div> + </div> + ); + } + + prepareChild() { + const {onSave, onCreate, children} = this.props; + + const additionalChildProps = {ref: 'editor'}; + if (onSave) { + additionalChildProps.onSave = onSave; + } + if (onCreate) { + additionalChildProps.onCreate = onCreate; + } + + const child = React.cloneElement(React.Children.only(children), additionalChildProps); + return child; + } + + + + handleSave = () => { + const childInstance = this.refs.editor.getWrappedInstance(); + if (childInstance.save) { + return childInstance.save(); + } else { + return this.props.onSave(); + } + }; + + handleCreate = () => { + const childInstance = this.refs.editor.getWrappedInstance(); + if (childInstance.create) { + childInstance.create(); + } else { + this.props.onCreate(); + } + } +} diff --git a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx new file mode 100644 index 0000000000..3ac3fcad28 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx @@ -0,0 +1,77 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import classnames from 'classnames'; +import Input from 'react-bootstrap/lib/Input'; + + +class ExpandableInput extends React.Component { + constructor(props){ + super(props); + this.state = {showInput: false, value: ''}; + this.toggleInput = this.toggleInput.bind(this); + this.handleFocus = this.handleFocus.bind(this); + this.handleInput = this.handleInput.bind(this); + this.handleClose = this.handleClose.bind(this); + } + + toggleInput(){ + if (!this.state.showInput){ + this.searchInputNode.refs.input.focus(); + } else { + this.setState({showInput: false}); + } + } + + handleInput(e){ + let {onChange} = this.props; + + this.setState({value: e.target.value}); + onChange(e); + } + + handleClose(){ + this.handleInput({target: {value: ''}}); + this.searchInputNode.refs.input.focus(); + } + + handleFocus(){ + if (!this.state.showInput){ + this.setState({showInput: true}); + } + } + + getValue(){ + return this.state.value; + } + + render(){ + let {iconType} = this.props; + + let inputClasses = classnames({ + 'expandable-active': this.state.showInput, + 'expandable-not-active': !this.state.showInput + }); + + let iconClasses = classnames( + 'expandable-icon', + {'expandable-icon-active': this.state.showInput} + ); + + return ( + <div className='expandable-input-wrapper'> + <Input + type='text' + value={this.state.value} + ref={(input) => this.searchInputNode = input} + className={inputClasses} + groupClassName='expandable-input-control' + onChange={e => this.handleInput(e)} + onFocus={this.handleFocus}/> + {this.state.showInput && this.state.value && <FontAwesome onClick={this.handleClose} name='close' className='expandable-close-button'/>} + {!this.state.value && <FontAwesome onClick={this.toggleInput} name={iconType} className={iconClasses}/>} + </div> + ); + } +} + +export default ExpandableInput; diff --git a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx new file mode 100644 index 0000000000..1036ac41c3 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx @@ -0,0 +1,52 @@ +/** + * 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 Select from 'react-select'; + +class SelectInput extends Component { + + inputValue = []; + + render() { + let {label, value, ...other} = this.props; + return ( + <div className='validation-input-wrapper dropdown-multi-select'> + <div className='form-group'> + {label && <label className='control-label'>{label}</label>} + <Select ref='_myInput' onChange={value => this.onSelectChanged(value)} {...other} value={value} /> + </div> + </div> + ); + } + + getValue() { + return this.inputValue && this.inputValue.length ? this.inputValue : ''; + } + + onSelectChanged(value) { + this.props.onMultiSelectChanged(value); + } + + componentDidMount() { + let {value} = this.props; + this.inputValue = value ? value : []; + } + componentDidUpdate() { + if (this.inputValue !== this.props.value) { + this.inputValue = this.props.value; + } + } +} + +export default SelectInput; diff --git a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx new file mode 100644 index 0000000000..873d3ded65 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx @@ -0,0 +1,53 @@ +import React from 'react'; + +export default +class ToggleInput extends React.Component { + + static propTypes = { + label: React.PropTypes.node, + value: React.PropTypes.bool, + onChange: React.PropTypes.func, + disabled: React.PropTypes.bool + } + + static defaultProps = { + value: false, + label: '' + } + + state = { + value: this.props.value + } + + status() { + return this.state.value ? 'on' : 'off'; + } + + render() { + let {label, disabled} = this.props; + let checked = this.status() === 'on'; + return ( + <div className='toggle-input-wrapper form-group' onClick={!disabled && this.click}> + <div className='toggle-input-label'>{label}</div> + <div className='toggle-switch'> + <input className='toggle toggle-round-flat' type='checkbox' checked={checked} readOnly/> + <label></label> + </div> + </div> + ); + } + + click = () => { + let value = !this.state.value; + this.setState({value}); + + let onChange = this.props.onChange; + if (onChange) { + onChange(value); + } + } + + getValue() { + return this.state.value; + } +} diff --git a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx new file mode 100644 index 0000000000..171bead9bb --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx @@ -0,0 +1,132 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import Input from 'react-bootstrap/lib/Input.js'; + +class DualListboxView extends React.Component { + + static propTypes = { + + availableList: React.PropTypes.arrayOf(React.PropTypes.shape({ + id: React.PropTypes.string.isRequired, + name: React.PropTypes.string.isRequired + })), + filterTitle: React.PropTypes.shape({ + left: React.PropTypes.string, + right: React.PropTypes.string + }), + selectedValuesList: React.PropTypes.arrayOf(React.PropTypes.string), + + onChange: React.PropTypes.func.isRequired + }; + + static defaultProps = { + selectedValuesList: [], + availableList: [], + filterTitle: { + left: '', + right: '' + } + }; + + state = { + availableListFilter: '', + selectedValuesListFilter: '' + }; + + static contextTypes = { + isReadOnlyMode: React.PropTypes.bool + }; + + render() { + let {availableList, selectedValuesList, filterTitle} = this.props; + let {availableListFilter, selectedValuesListFilter} = this.state; + let isReadOnlyMode = this.context.isReadOnlyMode; + + let unselectedList = availableList.filter(availableItem => !selectedValuesList.find(value => value === availableItem.id)); + let selectedList = availableList.filter(availableItem => selectedValuesList.find(value => value === availableItem.id)); + selectedList = selectedList.sort((a, b) => selectedValuesList.indexOf(a.id) - selectedValuesList.indexOf(b.id)); + + return ( + <div className='dual-list-box'> + {this.renderListbox(filterTitle.left, unselectedList, { + value: availableListFilter, + ref: 'availableListFilter', + disabled: isReadOnlyMode, + onChange: () => this.setState({availableListFilter: this.refs.availableListFilter.getValue()}) + }, {ref: 'availableValues', disabled: isReadOnlyMode})} + {this.renderOperationsBar(isReadOnlyMode)} + {this.renderListbox(filterTitle.right, selectedList, { + value: selectedValuesListFilter, + ref: 'selectedValuesListFilter', + disabled: isReadOnlyMode, + onChange: () => this.setState({selectedValuesListFilter: this.refs.selectedValuesListFilter.getValue()}) + }, {ref: 'selectedValues', disabled: isReadOnlyMode})} + </div> + ); + } + + renderListbox(filterTitle, list, filterProps, props) { + let regExFilter = new RegExp(escape(filterProps.value), 'i'); + let matchedItems = list.filter(item => item.name.match(regExFilter)); + let unMatchedItems = list.filter(item => !item.name.match(regExFilter)); + + + return ( + <div className='dual-search-multi-select-section'> + <p>{filterTitle}</p> + <div className='dual-text-box-search search-wrapper'> + <Input name='search-input-control' type='text' groupClassName='search-input-control' {...filterProps}/> + <FontAwesome name='search' className='search-icon'/> + </div> + <Input + multiple + groupClassName='dual-list-box-multi-select' + type='select' + name='dual-list-box-multi-select' + {...props}> + {matchedItems.map(item => this.renderOption(item.id, item.name))} + {matchedItems.length && unMatchedItems.length && <option style={{pointerEvents: 'none'}}>--------------------</option>} + {unMatchedItems.map(item => this.renderOption(item.id, item.name))} + </Input> + </div> + ); + } + + renderOption(value, name) { + return (<option className='dual-list-box-multi-select-text' key={value} value={value}>{name}</option>); + } + + renderOperationsBar(isReadOnlyMode) { + return ( + <div className={`dual-list-options-bar${isReadOnlyMode ? ' disabled' : ''}`}> + {this.renderOperationBarButton(() => this.addToSelectedList(), 'angle-right')} + {this.renderOperationBarButton(() => this.removeFromSelectedList(), 'angle-left')} + {this.renderOperationBarButton(() => this.addAllToSelectedList(), 'angle-double-right')} + {this.renderOperationBarButton(() => this.removeAllFromSelectedList(), 'angle-double-left')} + </div> + ); + } + + renderOperationBarButton(onClick, fontAwesomeIconName){ + return (<div className='dual-list-option' onClick={onClick}><FontAwesome name={fontAwesomeIconName}/></div>); + } + + addToSelectedList() { + this.props.onChange(this.props.selectedValuesList.concat(this.refs.availableValues.getValue())); + } + + removeFromSelectedList() { + const selectedValues = this.refs.selectedValues.getValue(); + this.props.onChange(this.props.selectedValuesList.filter(value => !selectedValues.find(selectedValue => selectedValue === value))); + } + + addAllToSelectedList() { + this.props.onChange(this.props.availableList.map(item => item.id)); + } + + removeAllFromSelectedList() { + this.props.onChange([]); + } +} + +export default DualListboxView; diff --git a/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx new file mode 100644 index 0000000000..5daaffea41 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx @@ -0,0 +1,221 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import classNames from 'classnames'; +import Select from 'nfvo-components/input/SelectInput.jsx'; + +export const other = {OTHER: 'Other'}; + +class InputOptions extends React.Component { + + static propTypes = { + values: React.PropTypes.arrayOf(React.PropTypes.shape({ + enum: React.PropTypes.string, + title: React.PropTypes.string + })), + isEnabledOther: React.PropTypes.bool, + title: React.PropTypes.string, + selectedValue: React.PropTypes.string, + multiSelectedEnum: React.PropTypes.array, + selectedEnum: React.PropTypes.string, + otherValue: React.PropTypes.string, + onEnumChange: React.PropTypes.func, + onOtherChange: React.PropTypes.func, + isRequired: React.PropTypes.bool, + isMultiSelect: React.PropTypes.bool + }; + + + static contextTypes = { + isReadOnlyMode: React.PropTypes.bool + }; + + state = { + otherInputDisabled: !this.props.otherValue + }; + + oldProps = { + selectedEnum: '', + otherValue: '', + multiSelectedEnum: [] + }; + + render() { + let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = this.props; + + let currentMultiSelectedEnum = []; + let currentSelectedEnum = ''; + let {otherInputDisabled} = this.state; + if (isMultiSelect) { + currentMultiSelectedEnum = multiSelectedEnum; + if(!otherInputDisabled) { + currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined; + } + } + else if(selectedEnum){ + currentSelectedEnum = selectedEnum; + } + + let isReadOnlyMode = this.context.isReadOnlyMode; + + return( + <div className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}> + {label && <label className='control-label'>{label}</label>} + {isMultiSelect && otherInputDisabled ? + <Select + ref='_myInput' + value={currentMultiSelectedEnum} + className='options-input' + clearable={false} + required={isRequired} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + onBlur={() => onBlur()} + onMultiSelectChanged={value => this.multiSelectEnumChanged(value)} + options={this.renderMultiSelectOptions(values)} + multi/> : + <div className={classNames('input-options',{'has-error' : hasError})}> + <select + ref={'_myInput'} + label={label} + className='form-control input-options-select' + value={currentSelectedEnum} + style={{'width' : otherInputDisabled ? '100%' : '95px'}} + onBlur={() => onBlur()} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + onChange={ value => this.enumChanged(value)} + type='select'> + {values && values.length && values.map(val => this.renderOptions(val))} + {onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>} + {children} + </select> + + {!otherInputDisabled && <div className='input-options-separator'/>} + <input + className='form-control input-options-other' + placeholder={i18n('other')} + ref='_otherValue' + style={{'display' : otherInputDisabled ? 'none' : 'block'}} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + value={otherValue || ''} + onBlur={() => onBlur()} + onChange={() => this.changedOtherInput()}/> + </div> + } + </div> + ); + } + + renderOptions(val){ + return( + <option key={val.enum} value={val.enum}>{val.title}</option> + ); + } + + + renderMultiSelectOptions(values) { + let {onOtherChange} = this.props; + let optionsList = []; + if (onOtherChange) { + optionsList = values.map(option => { + return { + label: option.title, + value: option.enum, + }; + }).concat([{ + label: i18n(other.OTHER), + value: i18n(other.OTHER), + }]); + } + else { + optionsList = values.map(option => { + return { + label: option.title, + value: option.enum, + }; + }); + } + if (optionsList.length > 0 && optionsList[0].value === '') { + optionsList.shift(); + } + return optionsList; + } + + getValue() { + let res = ''; + let {isMultiSelect} = this.props; + let {otherInputDisabled} = this.state; + + if (otherInputDisabled) { + res = isMultiSelect ? this.refs._myInput.getValue() : this.refs._myInput.value; + } else { + res = this.refs._otherValue.value; + } + return res; + } + + enumChanged() { + let enumValue = this.refs._myInput.value; + let {onEnumChange, isMultiSelect, onChange} = this.props; + this.setState({ + otherInputDisabled: enumValue !== other.OTHER + }); + + let value = isMultiSelect ? [enumValue] : enumValue; + if (onEnumChange) { + onEnumChange(value); + } + if (onChange) { + onChange(value); + } + } + + multiSelectEnumChanged(enumValue) { + let {onEnumChange} = this.props; + let selectedValues = enumValue.map(enumVal => { + return enumVal.value; + }); + + if (this.state.otherInputDisabled === false) { + selectedValues.shift(); + } + else if (selectedValues.includes(i18n(other.OTHER))) { + selectedValues = [i18n(other.OTHER)]; + } + + this.setState({ + otherInputDisabled: !selectedValues.includes(i18n(other.OTHER)) + }); + onEnumChange(selectedValues); + } + + changedOtherInput() { + let {onOtherChange} = this.props; + onOtherChange(this.refs._otherValue.value); + } + + componentDidUpdate() { + let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props; + if (this.oldProps.otherValue !== otherValue + || this.oldProps.selectedEnum !== selectedEnum + || this.oldProps.multiSelectedEnum !== multiSelectedEnum) { + this.oldProps = { + otherValue, + selectedEnum, + multiSelectedEnum + }; + onInputChange(); + } + } + + static getTitleByName(values, name) { + for (let key of Object.keys(values)) { + let option = values[key].find(option => option.enum === name); + if (option) { + return option.title; + } + } + return name; + } + +} + +export default InputOptions; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx new file mode 100644 index 0000000000..a87c8d6f40 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx @@ -0,0 +1,40 @@ +/** + * Holds the buttons for save/reset for forms. + * Used by the ValidationForm that changes the state of the buttons according to its own state. + * + * properties: + * labledButtons - whether or not to use labeled buttons or icons only + */ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Button from 'react-bootstrap/lib/Button.js'; +import FontAwesome from 'react-fontawesome'; + +class ValidationButtons extends React.Component { + + static propTypes = { + labledButtons: React.PropTypes.bool.isRequired, + isReadOnlyMode: React.PropTypes.bool + }; + + state = { + isValid: this.props.formValid + }; + + render() { + var submitBtn = this.props.labledButtons ? i18n('Save') : <FontAwesome className='check' name='check'/>; + var closeBtn = this.props.labledButtons ? i18n('Cancel') : <FontAwesome className='close' name='close'/>; + return ( + <div className='validation-buttons'> + {!this.props.isReadOnlyMode ? + <div> + <Button bsStyle='primary' ref='submitbutton' type='submit' disabled={!this.state.isValid}>{submitBtn}</Button> + <Button type='reset'>{closeBtn}</Button> + </div> + : <Button type='reset'>{i18n('Close')}</Button> + } + </div> + ); + } +} +export default ValidationButtons; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx new file mode 100644 index 0000000000..098ccf1fd4 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx @@ -0,0 +1,200 @@ +/** + * ValidationForm should be used in order to have a form that handles it's internal validation state. + * All ValidationInputs inside the form are checked for validity and the styling and submit buttons + * are updated accordingly. + * + * The properties that ahould be given to the form: + * labledButtons - whether or not use icons only as the form default buttons or use buttons with labels + * onSubmit - function for click on the submit button + * onReset - function for click on the reset button + */ +import React from 'react'; +import JSONSchema from 'nfvo-utils/json/JSONSchema.js'; +import JSONPointer from 'nfvo-utils/json/JSONPointer.js'; +import ValidationButtons from './ValidationButtons.jsx'; + +class ValidationForm extends React.Component { + + static childContextTypes = { + validationParent: React.PropTypes.any, + isReadOnlyMode: React.PropTypes.bool, + validationSchema: React.PropTypes.instanceOf(JSONSchema), + validationData: React.PropTypes.object + }; + + static defaultProps = { + hasButtons : true, + onSubmit : null, + onReset : null, + labledButtons: true, + onValidChange : null, + isValid: true + }; + + static propTypes = { + isValid : React.PropTypes.bool, + isReadOnlyMode : React.PropTypes.bool, + hasButtons : React.PropTypes.bool, + onSubmit : React.PropTypes.func, + onReset : React.PropTypes.func, + labledButtons: React.PropTypes.bool, + onValidChange : React.PropTypes.func, + onValidityChanged: React.PropTypes.func, + schema: React.PropTypes.object, + data: React.PropTypes.object + }; + + state = { + isValid: this.props.isValid + }; + + constructor(props) { + super(props); + this.validationComponents = []; + } + + componentWillMount() { + let {schema, data} = this.props; + if (schema) { + this.processSchema(schema, data); + } + } + + componentWillReceiveProps(nextProps) { + let {schema, data} = this.props; + let {schema: nextSchema, data: nextData} = nextProps; + + if (schema !== nextSchema || data !== nextData) { + if (!schema || !nextSchema) { + throw new Error('ValidationForm: dynamically adding/removing schema is not supported'); + } + + if (schema !== nextSchema) { + this.processSchema(nextSchema, nextData); + } else { + this.setState({data: nextData}); + } + } + } + + processSchema(rawSchema, rawData) { + let schema = new JSONSchema(); + schema.setSchema(rawSchema); + let data = schema.processData(rawData); + this.setState({ + schema, + data + }); + } + + render() { + // eslint-disable-next-line no-unused-vars + let {isValid, isReadOnlyMode, hasButtons, onSubmit, labledButtons, onValidChange, onValidityChanged, schema, data, children, ...formProps} = this.props; + return ( + <form {...formProps} onSubmit={event => this.handleFormSubmit(event)}> + <div className='validation-form-content'>{children}</div> + {hasButtons && <ValidationButtons labledButtons={labledButtons} ref='buttons' isReadOnlyMode={isReadOnlyMode}/>} + </form> + ); + } + + handleFormSubmit(event) { + event.preventDefault(); + let isFormValid = true; + this.validationComponents.forEach(validationComponent => { + const isInputValid = validationComponent.validate().isValid; + isFormValid = isInputValid && isFormValid; + }); + if(isFormValid && this.props.onSubmit) { + return this.props.onSubmit(event); + } else if(!isFormValid) { + this.setState({isValid: false}); + } + } + + componentWillUpdate(nextProps, nextState) { + if(this.state.isValid !== nextState.isValid && this.props.onValidityChanged) { + this.props.onValidityChanged(nextState.isValid); + } + } + + componentDidUpdate(prevProps, prevState) { + // only handling this programatically if the validation of the form is done outside of the view + // (example with a form that is dependent on the state of other forms) + if (prevProps.isValid !== this.props.isValid) { + if (this.props.hasButtons) { + this.refs.buttons.setState({isValid: this.state.isValid}); + } + } else if(this.state.isValid !== prevState.isValid) { + if (this.props.hasButtons) { + this.refs.buttons.setState({isValid: this.state.isValid}); + } + // callback in case form is part of bigger picture in view + if (this.props.onValidChange) { + this.props.onValidChange(this.state.isValid); + } + } + } + + componentDidMount() { + if (this.props.hasButtons) { + this.refs.buttons.setState({isValid: this.state.isValid}); + } + } + + + getChildContext() { + return { + validationParent: this, + isReadOnlyMode: this.props.isReadOnlyMode, + validationSchema: this.state.schema, + validationData: this.state.data + }; + } + + + /*** + * Used by ValidationInput in order to let the (parent) form know + * the valid state. If there is a change in the state of the form, + * the buttons will be updated. + * + * @param validationComponent + * @param isValid + */ + childValidStateChanged(validationComponent, isValid) { + if (isValid !== this.state.isValid) { + let oldState = this.state.isValid; + let newState = isValid && this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent).every(otherValidationComponent => { + return otherValidationComponent.isValid(); + }); + + if (oldState !== newState) { + this.setState({isValid: newState}); + } + } + } + + register(validationComponent) { + if (this.state.schema) { + // TODO: register + } else { + this.validationComponents.push(validationComponent); + } + } + + unregister(validationComponent) { + this.childValidStateChanged(validationComponent, true); + this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); + } + + onValueChanged(pointer, value, isValid, error) { + this.props.onDataChanged({ + data: JSONPointer.setValue(this.props.data, pointer, value), + isValid, + error + }); + } +} + + +export default ValidationForm; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx new file mode 100644 index 0000000000..0f14307645 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx @@ -0,0 +1,509 @@ +/** + * Used for inputs on a validation form. + * All properties will be passed on to the input element. + * + * The following properties can be set for OOB validations and callbacks: + - required: Boolean: Should be set to true if the input must have a value + - numeric: Boolean : Should be set to true id the input should be an integer + - onChange : Function : Will be called to validate the value if the default validations are not sufficient, should return a boolean value + indicating whether the value is valid + - didUpdateCallback :Function: Will be called after the state has been updated and the component has rerendered. This can be used if + there are dependencies between inputs in a form. + * + * The following properties of the state can be set to determine + * the state of the input from outside components: + - isValid : Boolean - whether the value is valid + - value : value for the input field, + - disabled : Boolean, + - required : Boolean - whether the input value must be filled out. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Validator from 'validator'; +import FormGroup from 'react-bootstrap/lib/FormGroup.js'; +import Input from 'react-bootstrap/lib/Input.js'; +import Overlay from 'react-bootstrap/lib/Overlay.js'; +import Tooltip from 'react-bootstrap/lib/Tooltip.js'; +import isEqual from 'lodash/isEqual.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import JSONSchema from 'nfvo-utils/json/JSONSchema.js'; +import JSONPointer from 'nfvo-utils/json/JSONPointer.js'; + + +import InputOptions from '../inputOptions/InputOptions.jsx'; + +const globalValidationFunctions = { + required: value => value !== '', + maxLength: (value, length) => Validator.isLength(value, {max: length}), + minLength: (value, length) => Validator.isLength(value, {min: length}), + pattern: (value, pattern) => Validator.matches(value, pattern), + numeric: value => { + if (value === '') { + // to allow empty value which is not zero + return true; + } + return Validator.isNumeric(value); + }, + maxValue: (value, maxValue) => value < maxValue, + minValue: (value, minValue) => value >= minValue, + alphanumeric: value => Validator.isAlphanumeric(value), + alphanumericWithSpaces: value => Validator.isAlphanumeric(value.replace(/ /g, '')), + validateName: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-/g, ''), 'en-US'), + validateVendorName: value => Validator.isAlphanumeric(value.replace(/[\x7F-\xFF]|\s/g, ''), 'en-US'), + freeEnglishText: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-|\,|\(|\)|\?/g, ''), 'en-US'), + email: value => Validator.isEmail(value), + ip: value => Validator.isIP(value), + url: value => Validator.isURL(value) +}; + +const globalValidationMessagingFunctions = { + required: () => i18n('Field is required'), + maxLength: (value, maxLength) => i18n('Field value has exceeded it\'s limit, {maxLength}. current length: {length}', { + length: value.length, + maxLength + }), + minLength: (value, minLength) => i18n('Field value should contain at least {minLength} characters.', {minLength}), + pattern: (value, pattern) => i18n('Field value should match the pattern: {pattern}.', {pattern}), + numeric: () => i18n('Field value should contain numbers only.'), + maxValue: (value, maxValue) => i18n('Field value should be less than: {maxValue}.', {maxValue}), + minValue: (value, minValue) => i18n('Field value should be at least: {minValue}.', {minValue}), + alphanumeric: () => i18n('Field value should contain letters or digits only.'), + alphanumericWithSpaces: () => i18n('Field value should contain letters, digits or spaces only.'), + validateName: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'), + validateVendorName: ()=> i18n('Field value should contain English letters digits and spaces only.'), + freeEnglishText: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'), + email: () => i18n('Field value should be a valid email address.'), + ip: () => i18n('Field value should be a valid ip address.'), + url: () => i18n('Field value should be a valid url address.'), + general: () => i18n('Field value is invalid.') +}; + +class ValidationInput extends React.Component { + + static contextTypes = { + validationParent: React.PropTypes.any, + isReadOnlyMode: React.PropTypes.bool, + validationSchema: React.PropTypes.instanceOf(JSONSchema), + validationData: React.PropTypes.object + }; + + static defaultProps = { + onChange: null, + disabled: null, + didUpdateCallback: null, + validations: {}, + value: '' + }; + + static propTypes = { + type: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func, + disabled: React.PropTypes.bool, + didUpdateCallback: React.PropTypes.func, + validations: React.PropTypes.object, + isMultiSelect: React.PropTypes.bool, + onOtherChange: React.PropTypes.func, + pointer: React.PropTypes.string + }; + + + state = { + isValid: true, + style: null, + value: this.props.value, + error: {}, + previousErrorMessage: '', + wasInvalid: false, + validations: this.props.validations, + isMultiSelect: this.props.isMultiSelect + }; + + componentWillMount() { + if (this.context.validationSchema) { + let {validationSchema: schema, validationData: data} = this.context, + {pointer} = this.props; + + if (!schema.exists(pointer)) { + console.error(`Field doesn't exists in the schema ${pointer}`); + } + + let value = JSONPointer.getValue(data, pointer); + if (value === undefined) { + value = schema.getDefault(pointer); + if (value === undefined) { + value = ''; + } + } + this.setState({value}); + + let enums = schema.getEnum(pointer); + if (enums) { + let values = enums.map(value => ({enum: value, title: value, groupName: pointer})), + isMultiSelect = schema.isArray(pointer); + + if (!isMultiSelect && this.props.type !== 'radiogroup') { + values = [{enum: '', title: i18n('Select...')}, ...values]; + } + if (isMultiSelect && Array.isArray(value) && value.length === 0) { + value = ''; + } + + this.setState({ + isMultiSelect, + values, + onEnumChange: value => this.changedInputOptions(value), + value + }); + } + + this.setState({validations: this.extractValidationsFromSchema(schema, pointer, this.props)}); + } + } + + extractValidationsFromSchema(schema, pointer, props) { + /* props are here to get precedence over the scheme definitions */ + let validations = {}; + + if (schema.isRequired(pointer)) { + validations.required = true; + } + + if (schema.isNumber(pointer)) { + validations.numeric = true; + + const maxValue = props.validations.maxValue || schema.getMaxValue(pointer); + if (maxValue !== undefined) { + validations.maxValue = maxValue; + } + + const minValue = props.validations.minValue || schema.getMinValue(pointer); + if (minValue !== undefined) { + validations.minValue = minValue; + } + } + + + if (schema.isString(pointer)) { + + const pattern = schema.getPattern(pointer); + if (pattern) { + validations.pattern = pattern; + } + + const maxLength = schema.getMaxLength(pointer); + if (maxLength !== undefined) { + validations.maxLength = maxLength; + } + + const minLength = schema.getMinLength(pointer); + if (minLength !== undefined) { + validations.minLength = minLength; + } + } + + return validations; + } + + componentWillReceiveProps({value: nextValue, validations: nextValidations, pointer: nextPointer}, nextContext) { + const {validations, value} = this.props; + const validationsChanged = !isEqual(validations, nextValidations); + if (nextContext.validationSchema) { + if (this.props.pointer !== nextPointer || + this.context.validationData !== nextContext.validationData) { + let currentValue = JSONPointer.getValue(this.context.validationData, this.props.pointer), + nextValue = JSONPointer.getValue(nextContext.validationData, nextPointer); + if(nextValue === undefined) { + nextValue = ''; + } + if (this.state.isMultiSelect && Array.isArray(nextValue) && nextValue.length === 0) { + nextValue = ''; + } + if (currentValue !== nextValue) { + this.setState({value: nextValue}); + } + if (validationsChanged) { + this.setState({ + validations: this.extractValidationsFromSchema(nextContext.validationSchema, nextPointer, {validations: nextValidations}) + }); + } + } + } else { + if (validationsChanged) { + this.setState({validations: nextValidations}); + } + if (this.state.wasInvalid && (value !== nextValue || validationsChanged)) { + this.validate(nextValue, nextValidations); + } else if (value !== nextValue) { + this.setState({value: nextValue}); + } + } + } + + shouldTypeBeNumberBySchemeDefinition(pointer) { + return this.context.validationSchema && + this.context.validationSchema.isNumber(pointer); + } + + hasEnum(pointer) { + return this.context.validationSchema && + this.context.validationSchema.getEnum(pointer); + } + + render() { + let {value, isMultiSelect, values, onEnumChange, style, isValid, validations} = this.state; + let {onOtherChange, type, pointer} = this.props; + if (this.shouldTypeBeNumberBySchemeDefinition(pointer) && !this.hasEnum(pointer)) { + type = 'number'; + } + let props = {...this.props}; + + let groupClasses = this.props.groupClassName || ''; + if (validations.required) { + groupClasses += ' required'; + } + let isReadOnlyMode = this.context.isReadOnlyMode; + + if (value === true && (type === 'checkbox' || type === 'radio')) { + props.checked = true; + } + return ( + <div className='validation-input-wrapper'> + { + !isMultiSelect && !onOtherChange && type !== 'select' && type !== 'radiogroup' + && <Input + {...props} + type={type} + groupClassName={groupClasses} + ref={'_myInput'} + value={value} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + bsStyle={style} + onChange={() => this.changedInput()} + onBlur={() => this.blurInput()}> + {this.props.children} + </Input> + } + { + type === 'radiogroup' + && <FormGroup> + { + values.map(val => + <Input disabled={isReadOnlyMode || Boolean(this.props.disabled)} + inline={true} + ref={'_myInput' + (typeof val.enum === 'string' ? val.enum.replace(/\W/g, '_') : val.enum)} + value={val.enum} checked={value === val.enum} + type='radio' label={val.title} + name={val.groupName} + onChange={() => this.changedInput()}/> + ) + } + </FormGroup> + } + { + (isMultiSelect || onOtherChange || type === 'select') + && <InputOptions + onInputChange={() => this.changedInput()} + onBlur={() => this.blurInput()} + hasError={!isValid} + ref={'_myInput'} + isMultiSelect={isMultiSelect} + values={values} + onEnumChange={onEnumChange} + selectedEnum={value} + multiSelectedEnum={value} + {...props} /> + } + {this.renderOverlay()} + </div> + ); + } + + renderOverlay() { + let position = 'right'; + if (this.props.type === 'text' + || this.props.type === 'email' + || this.props.type === 'number' + || this.props.type === 'password' + + ) { + position = 'bottom'; + } + + let validationMessage = this.state.error.message || this.state.previousErrorMessage; + return ( + <Overlay + show={!this.state.isValid} + placement={position} + target={() => { + let target = ReactDOM.findDOMNode(this.refs._myInput); + return target.offsetParent ? target : undefined; + }} + container={this}> + <Tooltip + id={`error-${validationMessage.replace(' ', '-')}`} + className='validation-error-message'> + {validationMessage} + </Tooltip> + </Overlay> + ); + } + + componentDidMount() { + if (this.context.validationParent) { + this.context.validationParent.register(this); + } + } + + componentDidUpdate(prevProps, prevState) { + if (this.context.validationParent) { + if (prevState.isValid !== this.state.isValid) { + this.context.validationParent.childValidStateChanged(this, this.state.isValid); + } + } + if (this.props.didUpdateCallback) { + this.props.didUpdateCallback(); + } + + } + + componentWillUnmount() { + if (this.context.validationParent) { + this.context.validationParent.unregister(this); + } + } + + isNumberInputElement() { + return this.props.type === 'number' || this.refs._myInput.props.type === 'number'; + } + + /*** + * Adding same method as the actual input component + * @returns {*} + */ + getValue() { + if (this.props.type === 'checkbox') { + return this.refs._myInput.getChecked(); + } + if (this.props.type === 'radiogroup') { + for (let key in this.refs) { // finding the value of the radio button that was checked + if (this.refs[key].getChecked()) { + return this.refs[key].getValue(); + } + } + } + if (this.isNumberInputElement()) { + return Number(this.refs._myInput.getValue()); + } + + return this.refs._myInput.getValue(); + } + + resetValue() { + this.setState({value: this.props.value}); + } + + + /*** + * internal method that validated the value. includes callback to the onChange method + * @param value + * @param validations - map containing validation id and the limitation describing the validation. + * @returns {object} + */ + validateValue = (value, validations) => { + let {customValidationFunction} = validations; + let error = {}; + let isValid = true; + for (let validation in validations) { + if ('customValidationFunction' !== validation) { + if (validations[validation]) { + if (!globalValidationFunctions[validation](value, validations[validation])) { + error.id = validation; + error.message = globalValidationMessagingFunctions[validation](value, validations[validation]); + isValid = false; + break; + } + } + } else { + let customValidationResult = customValidationFunction(value); + + if (customValidationResult !== true) { + error.id = 'custom'; + isValid = false; + if (typeof customValidationResult === 'string') {//custom validation error message supplied. + error.message = customValidationResult; + } else { + error.message = globalValidationMessagingFunctions.general(); + } + break; + } + + + } + } + + return { + isValid, + error + }; + }; + + /*** + * Internal method that handles the change event of the input. validates and updates the state. + */ + changedInput() { + + let {isValid, error} = this.state.wasInvalid ? this.validate() : this.state; + let onChange = this.props.onChange; + if (onChange) { + onChange(this.getValue(), isValid, error); + } + if (this.context.validationSchema) { + let value = this.getValue(); + if (this.state.isMultiSelect && value === '') { + value = []; + } + if (this.shouldTypeBeNumberBySchemeDefinition(this.props.pointer)) { + value = Number(value); + } + this.context.validationParent.onValueChanged(this.props.pointer, value, isValid, error); + } + } + + changedInputOptions(value) { + this.context.validationParent.onValueChanged(this.props.pointer, value, true); + } + + blurInput() { + if (!this.state.wasInvalid) { + this.setState({wasInvalid: true}); + } + + let {isValid, error} = !this.state.wasInvalid ? this.validate() : this.state; + let onBlur = this.props.onBlur; + if (onBlur) { + onBlur(this.getValue(), isValid, error); + } + } + + validate(value = this.getValue(), validations = this.state.validations) { + let validationStatus = this.validateValue(value, validations); + let {isValid, error} = validationStatus; + let _style = isValid ? null : 'error'; + this.setState({ + isValid, + error, + value, + previousErrorMessage: this.state.error.message || '', + style: _style, + wasInvalid: !isValid || this.state.wasInvalid + }); + + return validationStatus; + } + + isValid() { + return this.state.isValid; + } + +} +export default ValidationInput; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx new file mode 100644 index 0000000000..6036518288 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import Tab from 'react-bootstrap/lib/Tab.js'; + +export default +class ValidationTab extends React.Component { + + static propTypes = { + children: React.PropTypes.node, + eventKey: React.PropTypes.any.isRequired, + onValidationStateChange: React.PropTypes.func //This property is assigned dynamically via React.cloneElement. lookup ValidationTabs.jsx. therefore it cannot be stated as required! + }; + + constructor(props) { + super(props); + this.validationComponents = []; + } + + static childContextTypes = { + validationParent: React.PropTypes.any + }; + + static contextTypes = { + validationParent: React.PropTypes.any + }; + + getChildContext() { + return {validationParent: this}; + } + + state = { + isValid: true, + notifyParent: false + }; + + componentDidMount() { + let validationParent = this.context.validationParent; + if (validationParent) { + validationParent.register(this); + } + } + + componentWillUnmount() { + let validationParent = this.context.validationParent; + if (validationParent) { + validationParent.unregister(this); + } + } + + register(validationComponent) { + this.validationComponents.push(validationComponent); + } + + unregister(validationComponent) { + this.childValidStateChanged(validationComponent, true); + this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); + } + + notifyValidStateChangedToParent(isValid) { + + let validationParent = this.context.validationParent; + if (validationParent) { + validationParent.childValidStateChanged(this, isValid); + } + } + + childValidStateChanged(validationComponent, isValid) { + + const currentValidState = this.state.isValid; + if (isValid !== currentValidState) { + let filteredValidationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); + let newValidState = isValid && filteredValidationComponents.every(otherValidationComponent => { + return otherValidationComponent.isValid(); + }); + this.setState({isValid: newValidState, notifyParent: true}); + } + } + + validate() { + let isValid = true; + this.validationComponents.forEach(validationComponent => { + const isValidationComponentValid = validationComponent.validate().isValid; + isValid = isValidationComponentValid && isValid; + }); + this.setState({isValid, notifyParent: false}); + return {isValid}; + } + + componentDidUpdate(prevProps, prevState) { + if(prevState.isValid !== this.state.isValid) { + if(this.state.notifyParent) { + this.notifyValidStateChangedToParent(this.state.isValid); + } + this.props.onValidationStateChange(this.props.eventKey, this.state.isValid); + } + } + + isValid() { + return this.state.isValid; + } + + render() { + let {children, ...tabProps} = this.props; + return ( + <Tab {...tabProps}>{children}</Tab> + ); + } +} diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx new file mode 100644 index 0000000000..6eda4b9827 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Tabs from 'react-bootstrap/lib/Tabs.js'; +import Overlay from 'react-bootstrap/lib/Overlay.js'; +import Tooltip from 'react-bootstrap/lib/Tooltip.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export default +class ValidationTab extends React.Component { + + static propTypes = { + children: React.PropTypes.node + }; + + state = { + invalidTabs: [] + }; + + cloneTab(element) { + const {invalidTabs} = this.state; + return React.cloneElement( + element, + { + key: element.props.eventKey, + tabClassName: invalidTabs.indexOf(element.props.eventKey) > -1 ? 'invalid-tab' : 'valid-tab', + onValidationStateChange: (eventKey, isValid) => this.validTabStateChanged(eventKey, isValid) + } + ); + } + + validTabStateChanged(eventKey, isValid) { + let {invalidTabs} = this.state; + let invalidTabIndex = invalidTabs.indexOf(eventKey); + if (isValid && invalidTabIndex > -1) { + this.setState({invalidTabs: invalidTabs.filter(otherEventKey => eventKey !== otherEventKey)}); + } else if (!isValid && invalidTabIndex === -1) { + this.setState({invalidTabs: [...invalidTabs, eventKey]}); + } + } + + showTabsError() { + const {invalidTabs} = this.state; + return invalidTabs.length > 0 && (invalidTabs.length > 1 || invalidTabs[0] !== this.props.activeKey); + } + + render() { + return ( + <div> + <Tabs {...this.props} ref='tabsList'> + {this.props.children.map(element => this.cloneTab(element))} + </Tabs> + <Overlay + animation={false} + show={this.showTabsError()} + placement='bottom' + target={() => { + let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.active):nth-of-type(n)'); + return target && target.offsetParent ? target : undefined; + } + } + container={this}> + <Tooltip + id='error-some-tabs-contain-errors' + className='validation-error-message'> + {i18n('One or more tabs are invalid')} + </Tooltip> + </Overlay> + </div> + ); + } +} diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx new file mode 100644 index 0000000000..e8d0fc2536 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import store from 'sdc-app/AppStore.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; + +class ListEditorItem extends React.Component { + static propTypes = { + onSelect: React.PropTypes.func, + onDelete: React.PropTypes.func, + onEdit: React.PropTypes.func, + children: React.PropTypes.node, + isReadOnlyMode: React.PropTypes.bool + } + + render() { + let {onDelete, onSelect, onEdit, children, isReadOnlyMode} = this.props; + let isAbilityToDelete = isReadOnlyMode === undefined ? true : !isReadOnlyMode; + return ( + <div className='list-editor-item-view'> + <div className='list-editor-item-view-content' onClick={onSelect}> + {children} + </div> + <div className='list-editor-item-view-controller'> + {onEdit && <FontAwesome name='sliders' onClick={() => this.onClickedItem(onEdit)}/>} + {onDelete && isAbilityToDelete && <FontAwesome name='trash-o' onClick={() => this.onClickedItem(onDelete)}/>} + </div> + </div> + ); + } + + onClickedItem(callBackFunc) { + if(typeof callBackFunc === 'function') { + let {isCheckedOut} = this.props; + if (isCheckedOut === false) { + store.dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: {title: 'Error', msg: 'This item is checkedin/submitted, Click Check Out to continue'} + }); + } + else { + callBackFunc(); + } + } + } +} + +export default ListEditorItem; diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx new file mode 100644 index 0000000000..1ee91f31f6 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import Input from 'react-bootstrap/lib/Input.js'; + + +class ListEditorView extends React.Component { + + static defaultProps = { + className: '' + }; + + static propTypes = { + title: React.PropTypes.string, + plusButtonTitle: React.PropTypes.string, + children: React.PropTypes.node, + filterValue: React.PropTypes.string, + onFilter: React.PropTypes.func, + className: React.PropTypes.string, + isReadOnlyMode: React.PropTypes.bool, + placeholder: React.PropTypes.string + }; + + render() { + let {title, plusButtonTitle, onAdd, children, filterValue, onFilter, className, placeholder, isReadOnlyMode} = this.props; + return ( + <div className={`list-editor-view ${className}`}> + {title && onAdd && <div className='list-editor-view-title'>{title}</div>} + <div className='list-editor-view-actions'> + {title && !onAdd && <div className='list-editor-view-title-inline'>{title}</div>} + <div className={`list-editor-view-add-controller${isReadOnlyMode ? ' disabled' : ''}`} > + { onAdd && + <div onClick={onAdd}> + <span className='plus-icon-button pull-left'/> + <span>{plusButtonTitle}</span> + </div> + } + </div> + + { + onFilter && + <div className='list-editor-view-search search-wrapper'> + <Input + ref='filter' + type='text' + value={filterValue} + name='list-editor-view-search' + placeholder={placeholder} + groupClassName='search-input-control' + onChange={() => onFilter(this.refs.filter.getValue())}/> + <FontAwesome name='filter' className='filter-icon'/> + </div> + } + </div> + <div className='list-editor-view-list-scroller'> + <div className='list-editor-view-list'> + {children} + </div> + </div> + </div> + ); + } + +} +export default ListEditorView; diff --git a/openecomp-ui/src/nfvo-components/loader/Loader.jsx b/openecomp-ui/src/nfvo-components/loader/Loader.jsx new file mode 100644 index 0000000000..cc1ffdb2b3 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/loader/Loader.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import {connect} from 'react-redux'; + +const mapStateToProps = ({loader}) => { + return { + isLoading: loader.isLoading + }; +}; + +class Loader extends React.Component { + + static propTypes = { + isLoading: React.PropTypes.bool.isRequired + }; + + static defaultProps = { + isLoading: false + }; + + render() { + let {isLoading} = this.props; + + return ( + <div className='onboarding-loader'> + { + isLoading && <div className='onboarding-loader-backdrop'> + <div className='tlv-loader large'></div> + </div> + } + </div> + ); + } +} + +export default connect(mapStateToProps) (Loader); diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js new file mode 100644 index 0000000000..e8e4953eb9 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js @@ -0,0 +1,26 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + SHOW: null, + HIDE: null +}); diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js new file mode 100644 index 0000000000..582eff330d --- /dev/null +++ b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LoaderConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.SHOW: + return {isLoading: true}; + case actionTypes.HIDE: + return {isLoading: false}; + default: + return state; + } +}; diff --git a/openecomp-ui/src/nfvo-components/modal/Modal.jsx b/openecomp-ui/src/nfvo-components/modal/Modal.jsx new file mode 100644 index 0000000000..be4963ef65 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/modal/Modal.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import BootstrapModal from 'react-bootstrap/lib/Modal.js'; + +let nextModalId = 0; + +export default class Modal extends React.Component { + + static Header = BootstrapModal.Header; + + static Title = BootstrapModal.Title; + + static Footer = BootstrapModal.Footer; + + static Body = class ModalBody extends React.Component { + + render() { + let {children, ...props} = this.props; + return ( + <BootstrapModal.Body {...props}> + {children} + </BootstrapModal.Body> + ); + } + + componentDidMount() { + let element = ReactDOM.findDOMNode(this); + element.addEventListener('click', event => { + if (event.target.tagName === 'A') { + event.preventDefault(); + } + }); + ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType => + element.addEventListener(eventType, event => event.stopPropagation()) + ); + } + }; + + componentWillMount() { + this.modalId = `dox-ui-modal-${nextModalId++}`; + } + + componentDidMount() { + this.ensureRootClass(); + } + + componentDidUpdate() { + this.ensureRootClass(); + } + + ensureRootClass() { + let element = document.getElementById(this.modalId); + while(element && !element.hasAttribute('data-reactroot')) { + element = element.parentElement; + } + if (element && !element.classList.contains('dox-ui')) { + element.classList.add('dox-ui'); + } + } + + render() { + let {children, ...props} = this.props; + return ( + <BootstrapModal {...props} id={this.modalId}> + {children} + </BootstrapModal> + ); + } +} diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js b/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js new file mode 100644 index 0000000000..1a53f4c135 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js @@ -0,0 +1,29 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export default keyMirror({ + NOTIFY_ERROR: null, + NOTIFY_SUCCESS: null, + NOTIFY_WARNING: null, + NOTIFY_INFO: null, + NOTIFY_CLOSE: null +}); diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx b/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx new file mode 100644 index 0000000000..71793097fb --- /dev/null +++ b/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx @@ -0,0 +1,100 @@ +/** + * NotificationModal options: + * + * show: whether to show notification or not, + * type: the type of the notification. valid values are: 'default', 'error', 'warning', 'success' + * msg: the notification content. could be a string or node (React component) + * title: the notification title + * timeout: timeout for the notification to fade out. if timeout == 0 then the notification is rendered until the user closes it + * + */ +import React, {Component, PropTypes} from 'react'; +import {connect} from 'react-redux'; +import Button from 'react-bootstrap/lib/Button.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import SubmitErrorResponse from 'nfvo-components/SubmitErrorResponse.jsx'; +import NotificationConstants from './NotificationConstants.js'; + +let typeClass = { + 'default': 'primary', + error: 'danger', + warning: 'warning', + success: 'success' +}; + +const mapActionsToProps = (dispatch) => { + return {onCloseClick: () => dispatch({type: NotificationConstants.NOTIFY_CLOSE})}; +}; + +const mapStateToProps = ({notification}) => { + + let show = notification !== null && notification.title !== 'Conflict'; + let mapResult = {show}; + if (show) { + mapResult = {show, ...notification}; + } + + return mapResult; +}; + +export class NotificationModal extends Component { + + static propTypes = { + show: PropTypes.bool, + type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), + title: PropTypes.string, + msg: PropTypes.node, + validationResponse: PropTypes.object, + timeout: PropTypes.number + }; + + static defaultProps = { + show: false, + type: 'default', + title: '', + msg: '', + timeout: 0 + }; + + state = {type: undefined}; + + componentWillReceiveProps(nextProps) { + if (this.props.show !== nextProps.show && nextProps.show === false) { + this.setState({type: this.props.type}); + } + else { + this.setState({type: undefined}); + } + } + + componentDidUpdate() { + if (this.props.timeout) { + setTimeout(this.props.onCloseClick, this.props.timeout); + } + } + + render() { + let {title, type, msg, show, validationResponse, onCloseClick} = this.props; + if (!show) { + type = this.state.type; + } + if (validationResponse) { + msg = (<SubmitErrorResponse validationResponse={validationResponse}/>); + } + return ( + <Modal show={show} className={`notification-modal ${typeClass[type]}`}> + <Modal.Header> + <Modal.Title>{title}</Modal.Title> + </Modal.Header> + <Modal.Body>{msg}</Modal.Body> + <Modal.Footer> + <Button bsStyle={typeClass[type]} onClick={onCloseClick}>{i18n('OK')}</Button> + </Modal.Footer> + </Modal> + ); + } +} + +export default connect(mapStateToProps, mapActionsToProps)(NotificationModal); diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js b/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js new file mode 100644 index 0000000000..c8b30d6e50 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js @@ -0,0 +1,51 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import NotificationConstants from './NotificationConstants.js'; + +export default (state = null, action) => { + switch (action.type) { + case NotificationConstants.NOTIFY_INFO: + return {type: 'default', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout}; + + case NotificationConstants.NOTIFY_ERROR: + return { + type: 'error', + title: action.data.title, + msg: action.data.msg, + validationResponse: action.data.validationResponse, + timeout: action.data.timeout + }; + + case NotificationConstants.NOTIFY_WARNING: + return {type: 'warning', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout}; + + case NotificationConstants.NOTIFY_SUCCESS: + return { + type: 'success', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout + }; + case NotificationConstants.NOTIFY_CLOSE: + return null; + + default: + return state; + } + +}; diff --git a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx new file mode 100644 index 0000000000..feb0f813ea --- /dev/null +++ b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import classnames from 'classnames'; +import Collapse from 'react-bootstrap/lib/Collapse.js'; + +class NavigationSideBar extends React.Component { + + static PropTypes = { + activeItemId: React.PropTypes.string.isRequired, + onSelect: React.PropTypes.func, + onToggle: React.PropTypes.func, + groups: React.PropTypes.array + }; + + render() { + let {groups, activeItemId} = this.props; + + return ( + <div className='navigation-side-content'> + {groups.map(group => ( + <div className='navigation-group' key={group.id}> + <div className='group-name'>{group.name}</div> + <div className='navigation-group-items'> + { + group.items && group.items.map(item => this.renderGroupItem(item, activeItemId)) + } + </div> + </div> + ))} + </div> + ); + } + + renderGroupItem(item, activeItemId) { + let isGroup = item.items && item.items.length > 0; + return ( + <div className={classnames('navigation-group-item', {'selected-item': item.id === activeItemId})}> + <div + key={item.id} + className={classnames('navigation-group-item-name', { + 'selected': item.id === activeItemId, + 'disabled': item.disabled, + 'bold-name': item.expanded, + 'hidden': item.hidden + })} + onClick={(event) => this.handleItemClicked(event, item)}> + {item.name} + </div> + {isGroup && + <Collapse in={item.expanded}> + <div> + {item.items.map(item => this.renderGroupItem(item, activeItemId))} + </div> + </Collapse> + } + </div> + ); + } + + handleItemClicked(event, item) { + event.stopPropagation(); + if(this.props.onToggle) { + this.props.onToggle(this.props.groups, item.id); + } + if(item.onSelect) { + item.onSelect(); + } + if(this.props.onSelect) { + this.props.onSelect(item); + } + } +} + +export default NavigationSideBar; diff --git a/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx b/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx new file mode 100644 index 0000000000..10c5326300 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import ReactDOM from 'react-dom'; + +class SlidePanel extends React.Component { + + static PropTypes = { + direction: React.PropTypes.string.isRequired, + className: React.PropTypes.string, + title: React.PropTypes.string, + isOpen: React.PropTypes.bool + }; + + static defaultProps = { + title: '', + className: '', + isOpen: true + }; + + state = { + isOpen: this.props.isOpen, + direction: this.props.direction, + width: 0, + arrowWidth: 0 + }; + + componentDidMount() { + this.setSliderPosition(); + } + + componentDidUpdate() { + this.setSliderPosition(); + } + + render() { + + let {children, className} = this.props; + let {isOpen} = this.state; + + return ( + <div className={ `slide-panel ${className}`}> + {this.renderHeader(isOpen)} + <div className={'slide-panel-content ' + (isOpen ? 'opened' : 'closed')}>{children}</div> + </div> + ); + } + + renderHeader(isOpen) { + let {direction: initialDirection, title} = this.props; + let {direction: currentDirection} = this.state; + + let iconName = currentDirection === 'right' ? 'angle-double-right collapse-double-icon' : 'angle-double-left collapse-double-icon'; + + let awestyle = {padding: '5px'}; + + if (!isOpen && initialDirection === 'right') { + awestyle.marginLeft = '-1px'; + } + return ( + <div className='slide-panel-header'> + { initialDirection === 'left' && <span className='slide-panel-header-title'>{title}</span>} + <FontAwesome + ref='arrowIcon' + style={awestyle} + onClick={this.handleClick} + className='pull-right' + name={iconName} + size='2x'/> + { initialDirection === 'right' && <span className='slide-panel-header-title'>{title}</span>} + </div> + ); + } + + handleClick = () => { + this.setState({ + isOpen: !this.state.isOpen, + direction: this.state.direction === 'left' ? 'right' : 'left' + }); + } + + setSliderPosition = () => { + + let el = ReactDOM.findDOMNode(this); + let {style} = el; + + let {direction: initialDirection} = this.props; + let arrowIconSize = Math.floor(ReactDOM.findDOMNode(this.refs.arrowIcon).getBoundingClientRect().width) * 2; + if (!this.state.isOpen) { + if (this.props.direction === 'left') { + style.left = arrowIconSize - el.getBoundingClientRect().width + 'px'; + } + if (initialDirection === 'right') { + style.right = arrowIconSize - el.getBoundingClientRect().width + 'px'; + } + } + else { + if (initialDirection === 'left') { + style.left = '0px'; + } + + if (this.props.direction === 'right') { + style.right = '0px'; + } + } + } + +} + +export default SlidePanel;
\ No newline at end of file diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx new file mode 100644 index 0000000000..78525f84c6 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx @@ -0,0 +1,165 @@ +import React from 'react'; +import classnames from 'classnames'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import Navbar from 'react-bootstrap/lib/Navbar.js'; +import Nav from 'react-bootstrap/lib/Nav.js'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import {actionsEnum, statusEnum} from './VersionControllerConstants.js'; + + +class VersionController extends React.Component { + + static propTypes = { + version: React.PropTypes.string, + viewableVersions: React.PropTypes.array, + onVersionSwitching: React.PropTypes.func, + isCheckedOut: React.PropTypes.bool.isRequired, + status: React.PropTypes.string.isRequired, + callVCAction: React.PropTypes.func, + onSave: React.PropTypes.func, + onClose: React.PropTypes.func, + isFormDataValid: React.PropTypes.bool + }; + + render() { + let {status, isCheckedOut, version = '', viewableVersions = [], onVersionSwitching, callVCAction, onSave, isFormDataValid, onClose} = this.props; + let isCheckedIn = Boolean(status === statusEnum.CHECK_IN_STATUS); + let isLatestVersion = Boolean(version === viewableVersions[viewableVersions.length - 1]); + if (!isLatestVersion) { + status = statusEnum.PREVIOUS_VERSION; + } + + return ( + <div className='version-controller-bar'> + <Navbar inverse className='navbar'> + <Navbar.Collapse> + <Nav className='items-in-left'> + <div className='version-section'> + <ValidationInput + type='select' + selectedEnum={version} + onEnumChange={value => onVersionSwitching && onVersionSwitching(value)}> + {viewableVersions && viewableVersions.map(viewVersion => { + return ( + <option key={viewVersion} value={viewVersion}>{`V ${viewVersion}`}</option> + ); + }) + } + {!viewableVersions.includes(version) && + <option key={version} value={version}>{`V ${version}`}</option>} + </ValidationInput> + </div> + <div className='vc-status'> + <div className='onboarding-status-icon'></div> + <div className='status-text'> {i18n('ONBOARDING')} + <div className='status-text-dash'> -</div> + </div> + {this.renderStatus(status)} + </div> + </Nav> + <Nav pullRight> + <div className='items-in-right'> + <div className='action-buttons'> + {callVCAction && + <div className='version-control-buttons'> + <div + className={classnames('vc-nav-item-button button-submit', {'disabled': !isCheckedIn || !isLatestVersion})} + onClick={() => this.submit(callVCAction)}> + {i18n('Submit')} + </div> + <div + className={classnames('vc-nav-item-button button-checkin-checkout', {'disabled': status === statusEnum.LOCK_STATUS || !isLatestVersion})} + onClick={() => this.checkinCheckoutVersion(callVCAction)}> + {`${isCheckedOut ? i18n('Check In') : i18n('Check Out')}`} + </div> + <div + className={classnames('sprite-new revert-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || version === '0.1' || !isLatestVersion})} + onClick={() => this.revertCheckout(callVCAction)}> + </div> + </div> + } + {onSave && + <div + className={classnames('sprite-new save-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || !isFormDataValid || !isLatestVersion})} + onClick={() => onSave()}> + </div> + } + </div> + <div className='vc-nav-item-close' onClick={() => onClose && onClose()}> X</div> + </div> + </Nav> + </Navbar.Collapse> + </Navbar> + </div> + ); + } + + renderStatus(status) { + switch (status) { + case statusEnum.CHECK_OUT_STATUS: + return ( + <div className='checkout-status-icon'> + <div className='catalog-tile-check-in-status sprite-new checkout-editable-status-icon'></div> + <div className='status-text'> {i18n('CHECKED OUT')} </div> + </div> + ); + case statusEnum.LOCK_STATUS: + return ( + <div className='status-text'> {i18n('LOCKED')} </div> + ); + case statusEnum.CHECK_IN_STATUS: + return ( + <div className='status-text'> {i18n('CHECKED IN')} </div> + ); + case statusEnum.SUBMIT_STATUS: + return ( + <div className='status-text'> {i18n('SUBMITTED')} </div> + ); + default: + return ( + <div className='status-text'> {i18n(status)} </div> + ); + } + } + + checkinCheckoutVersion(callVCAction) { + if (this.props.isCheckedOut) { + this.checkin(callVCAction); + } + else { + this.checkout(callVCAction); + } + } + + checkin(callVCAction) { + + const action = actionsEnum.CHECK_IN; + + if (this.props.onSave) { + this.props.onSave().then(()=>{ + callVCAction(action); + }); + }else{ + callVCAction(action); + } + + } + + checkout(callVCAction) { + const action = actionsEnum.CHECK_OUT; + callVCAction(action); + } + + submit(callVCAction) { + const action = actionsEnum.SUBMIT; + callVCAction(action); + } + + revertCheckout(callVCAction) { + const action = actionsEnum.UNDO_CHECK_OUT; + callVCAction(action); + } +} + +export default VersionController; diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js new file mode 100644 index 0000000000..9251fd12c4 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionsEnum = keyMirror({ + CHECK_IN: 'Checkin', + CHECK_OUT: 'Checkout', + UNDO_CHECK_OUT: 'Undo_Checkout', + SUBMIT: 'Submit', + CREATE_PACKAGE: 'Create_Package' +}); + +export const statusEnum = keyMirror({ + CHECK_OUT_STATUS: 'Locked', + CHECK_IN_STATUS: 'Available', + SUBMIT_STATUS: 'Final', + LOCK_STATUS: 'LockedByUser', + PREVIOUS_VERSION: 'READ ONLY' +}); + diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js new file mode 100644 index 0000000000..de9914454c --- /dev/null +++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import Configuration from 'sdc-app/config/Configuration.js'; +import {statusEnum} from './VersionControllerConstants.js'; + + +const VersionControllerUtils = { + + getCheckOutStatusKindByUserID(status, lockingUser) { + let currentLoginUserID = Configuration.get('ATTUserID'); + let isCheckedOut = currentLoginUserID === lockingUser; + + return { + status: isCheckedOut ? status : statusEnum.LOCK_STATUS, + isCheckedOut + }; + }, + + isCheckedOutByCurrentUser(resource) { + let currentLoginUserID = Configuration.get('ATTUserID'); + return resource.lockingUser !== undefined && resource.lockingUser === currentLoginUserID; + }, + + isReadOnly(resource) { + const {version, viewableVersions = []} = resource; + const latestVersion = viewableVersions[viewableVersions.length - 1]; + return version !== latestVersion || !VersionControllerUtils.isCheckedOutByCurrentUser(resource); + } +}; + +export default VersionControllerUtils; diff --git a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx new file mode 100644 index 0000000000..d786aeef8b --- /dev/null +++ b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +class ProgressBar extends React.Component { + static propTypes = { + label: React.PropTypes.string, + now: React.PropTypes.string.isRequired + } + render() { + let {label, now} = this.props; + + return( + <div className='progress-bar-view'> + <div className='progress-bar-outside'> + <div style={{width: now + '%'}} className='progress-bar-inside'></div> + </div> + <div className='progress-bar-view-label'>{label}</div> + </div> + ); + } +} + +export default ProgressBar; diff --git a/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js new file mode 100644 index 0000000000..0d27204bef --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js @@ -0,0 +1,72 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import store from 'sdc-app/AppStore.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; + +function showVariablesInMessage(variables, msg) { + let regex; + variables.forEach((value, index) => { + value = value.replace(';', ','); + regex = new RegExp('\'\%' + (index + 1) + '\''); + msg = msg.replace(regex, value); + }); + return msg; +} + +function parseATTExceptionObject(responseJSON) { + let title, msg; + if (responseJSON.requestError && responseJSON.requestError.policyException) { + title = 'Error: ' + responseJSON.requestError.policyException.messageId; + msg = responseJSON.requestError.policyException.text; + } + else if (responseJSON.requestError && responseJSON.requestError.serviceException) { + title = 'Error: ' + responseJSON.requestError.serviceException.messageId; + msg = responseJSON.requestError.serviceException.text; + let {variables} = responseJSON.requestError.serviceException; + if (variables) { + msg = showVariablesInMessage(variables, msg); + } + } + else { + title = responseJSON.status; + msg = responseJSON.message; + } + return {title, msg}; +} + +var errorResponseHandler = (xhr/*, textStatus, errorThrown*/) => { + let errorData; + if (xhr.responseJSON) { + errorData = parseATTExceptionObject(xhr.responseJSON); + } + else { + errorData = { + title: xhr.statusText, + msg: xhr.responseText + }; + } + store.dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: {...errorData} + }); +}; + +export default errorResponseHandler; diff --git a/openecomp-ui/src/nfvo-utils/KeyMirror.js b/openecomp-ui/src/nfvo-utils/KeyMirror.js new file mode 100644 index 0000000000..eb50d31e07 --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/KeyMirror.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var keyMirror = function (obj) { + var ret = {}; + var key; + var val; + if (!(obj instanceof Object && !Array.isArray(obj))) { + throw new Error('keyMirror(...): Argument must be an object.'); + } + for (key in obj) { + if (obj.hasOwnProperty(key)) { + val = obj[key]; + if (val instanceof Object) { + ret[key] = keyMirror(obj[key]); + } else if(val !== null && val !== undefined){ + ret[key] = val; + } + else { + ret[key] = Symbol(key); + } + } + } + return Object.freeze(ret); +}; + +export default keyMirror; diff --git a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js new file mode 100644 index 0000000000..24734739a2 --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js @@ -0,0 +1,288 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import _extend from 'lodash/extend.js'; +import _clone from 'lodash/clone.js'; +import _defaults from 'lodash/defaults.js'; +import $ from 'jquery'; +import uuid from 'uuid-js'; +import md5 from 'md5'; + +import store from 'sdc-app/AppStore.js'; +import {actionTypes as LoaderConstants} from 'nfvo-components/loader/LoaderConstants.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import errorResponseHandler from './ErrorResponseHandler.js'; + +const methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'delete': 'DELETE', + 'read': 'GET' +}; +const AUTHORIZATION_HEADER = 'X-AUTH-TOKEN'; +const STORAGE_AUTH_KEY = 'sdc-auth-token'; +const REQUEST_ID_HEADER = 'X-ECOMP-RequestID'; +const CONTENT_MD5_HEADER = 'Content-MD5'; +const namedParam = /{(\w+)}/g; +const queryParamsNames = { + pageStart: 'pageStart', + pageSize: 'pageSize', + sortField: 'sortField', + sortDir: 'sortDir', + filtering: 'filter' +}; + + +// jQuery binary transport to download files through XHR +// http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ +// https://github.com/henrya/js-jquery/tree/master/BinaryTransport +$.ajaxTransport('+binary', function (options/*, originalOptions , jqXHR*/) { + // check for conditions and support for blob / arraybuffer response type + if (window.FormData && ((options.dataType && (options.dataType === 'binary')) || + (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || + (window.Blob && options.data instanceof Blob)))) + ) { + return { + // create new XMLHttpRequest + send: function (headers, callback) { + // setup all variables + var xhr = new XMLHttpRequest(), + url = options.url, + type = options.type, + async = options.async || true, + // blob or arraybuffer. Default is blob + dataType = options.responseType || 'blob', + data = options.data || null, + username = options.username || null, + password = options.password || null; + + xhr.addEventListener('load', function () { + var data = {}; + data[options.dataType] = xhr.response; + // make callback and send data + callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders()); + }); + + xhr.open(type, url, async, username, password); + + // setup custom headers + for (var i in headers) { + xhr.setRequestHeader(i, headers[i]); + } + + xhr.responseType = dataType; + xhr.send(data); + }, + abort: function () { + } + }; + } +}); + +$(document).ajaxStart(()=> store.dispatch({type: LoaderConstants.SHOW})); +$(document).ajaxStop(()=> store.dispatch({type: LoaderConstants.HIDE})); + +function urlError() { + throw new Error('A "url" property or function must be specified'); +}; + +export function makeQueryParams(options) { + var qParams = {}; + if (options.pagination) { + qParams[queryParamsNames.pageStart] = options.pagination.pageStart; + qParams[queryParamsNames.pageSize] = options.pagination.pageSize; + } + if (options.sorting) { + qParams[queryParamsNames.sortField] = options.sorting.sortField; + qParams[queryParamsNames.sortDir] = options.sorting.sortDir; + } + if (options.filtering) { + qParams[queryParamsNames.filtering] = JSON.stringify(options.filtering); + } + + return _defaults(qParams, options.qParams); +} + +function appendQueryParam(p, value) { + var str = ''; + + if (value instanceof Array) { + if (value.length === 1) { + str = appendQueryParam(p, value[0]); + } else if (value.length > 1) { + str = appendQueryParam(p, value.shift()) + '&' + appendQueryParam(p, value); + } + } else { + str = p + '=' + encodeURIComponent(value); + } + + return str; +} + +function appendQueryString(url, qParams) { + var str = ''; + for (var param in qParams) { + str += (str ? '&' : '') + appendQueryParam(param, qParams[param]); + } + return url + (str ? '?' : '') + str; +} + +function composeURL(baseUrl, options) { + var url = baseUrl || urlError(); + if (options.url) { + delete options.url; + } + + var qParams = makeQueryParams(options); + url = appendQueryString(url, qParams); + + var matches = url.match(namedParam); + if (matches) { + for (var i = 0; i < matches.length; i++) { + var param = matches[i].substring(1, matches[i].length - 1); + var value = (options.params && options.params[param]); + + if (value === undefined) { + value = options[param]; + } + url = url.replace(matches[i], encodeURIComponent(value)); + } + } + + return url; +} + +function applyMD5Header(options, data) { + if (options.md5) { + let headers = options.headers; + headers[CONTENT_MD5_HEADER] = window.btoa(md5(JSON.stringify(data)).toLowerCase()); + } +} + +function applySecurity(options, data) { + var headers = options.headers || (options.headers = {}); + + var authToken = localStorage.getItem(STORAGE_AUTH_KEY); + if (authToken) { + headers[AUTHORIZATION_HEADER] = authToken; + } + + var attApiHeaders = Configuration.get('ATTApiHeaders'), + attUidHeader = attApiHeaders && attApiHeaders.userId; + if (attUidHeader) { + headers[attUidHeader.name] = attUidHeader.value; + } + + headers[REQUEST_ID_HEADER] = uuid.create().toString(); + + applyMD5Header(options, data); +} + +function handleResponse(options) { + var authToken = options.xhr.getResponseHeader(AUTHORIZATION_HEADER); + var prevToken = options.headers && options.headers[AUTHORIZATION_HEADER]; + if (authToken && authToken !== prevToken) { + if (authToken === 'null') { + localStorage.removeItem(STORAGE_AUTH_KEY); + } else { + localStorage.setItem(STORAGE_AUTH_KEY, authToken); + } + } +} + +function sync(baseUrl, method, options, data) { + + options = options ? _clone(options) : {}; + + var type = methodMap[method]; + _defaults(options || (options = {})); + var params = { + type: type, + dataType: 'json' + }; + params.url = composeURL(baseUrl, options); + + if ((method === 'create' || method === 'update') && data instanceof FormData) { + params.contentType = 'multipart/form-data'; + params.data = data; + } + else if (method === 'create' || method === 'update') { + params.contentType = 'application/json'; + params.data = JSON.stringify(data); + } + + if (params.type !== 'GET') { + params.processData = false; + } + var success = options.success; + options.success = function (resp) { + if (success) { + handleResponse(options); + success.call(options.context, _clone(resp), resp, options); + } + }; + + options.error = options.error || errorResponseHandler; + + if (typeof options.progressCallback === 'function' && options.fileSize) { + const {fileSize} = options; + options.xhrFields = { + // add listener to XMLHTTPRequest object directly for progress (jquery doesn't have this yet) + onprogress: function (progress) { + // calculate upload progress + let percentage = Math.floor((progress.loaded / fileSize) * 100); + // log upload progress to console + //console.log('progress', percentage); + options.progressCallback(percentage); + if (percentage === 100) { + console.log('DONE!'); + } + } + }; + } + + applySecurity(options, data); + + if (DEBUG) { + console.log('--> Making REST call (' + type + '): ' + params.url); + } + var xhr = options.xhr = $.ajax(_extend(params, options)); + return xhr; +} + +export default { + + fetch(baseUrl, options) { + return sync(baseUrl, 'read', options); + }, + + save(baseUrl, data, options) { + return sync(baseUrl, 'update', options, data); + }, + + create(baseUrl, data, options) { + return sync(baseUrl, 'create', options, data); + }, + + destroy(baseUrl, options) { + return sync(baseUrl, 'delete', options); + } + +}; diff --git a/openecomp-ui/src/nfvo-utils/UUID.js b/openecomp-ui/src/nfvo-utils/UUID.js new file mode 100644 index 0000000000..314c98ba6f --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/UUID.js @@ -0,0 +1,58 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import UUID from 'uuid-js'; + +let toCustomUUID = (uuid) => { + return 'U' + uuid.replace(/-/g, ''); +}; + +let getUUID = function(num, isSync) { + if (isSync) { + let uuid; + if (!num) { + uuid = toCustomUUID(UUID.create().toString()); + } else { + uuid = []; + for (var i = 0; i < num; i++) { + uuid[i] = toCustomUUID(UUID.create().toString()); + } + } + if (num === 1) { + return uuid[0]; + } else { + return uuid; + } + } + return new Promise(resolve => { + let uuid; + if (!num) { + uuid = toCustomUUID(UUID.create().toString()); + } else { + uuid = []; + for (var i = 0; i < num; i++) { + uuid[i] = toCustomUUID(UUID.create().toString()); + } + } + setTimeout(() => resolve(uuid), 100); + }); +}; + +export default getUUID; diff --git a/openecomp-ui/src/nfvo-utils/i18n/i18n.js b/openecomp-ui/src/nfvo-utils/i18n/i18n.js new file mode 100644 index 0000000000..64587713b7 --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/i18n/i18n.js @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import IntlObj from 'intl'; +import IntlMessageFormatObj from 'intl-messageformat'; +import IntlRelativeFormatObj from 'intl-relativeformat'; +import createFormatCacheObj from 'intl-format-cache'; +import i18nJson from 'i18nJson'; + +/* + Intl libs are using out dated transpailer from ecmascript6. +* TODO: As soon as they fix it, remove this assignments!!! +* */ +var Intl = window.Intl || IntlObj.default, + IntlMessageFormat = IntlMessageFormatObj.default, + IntlRelativeFormat = IntlRelativeFormatObj.default, + createFormatCache = createFormatCacheObj.default; + +var i18nData; + +if(i18nJson) { + i18nData = i18nJson.dataWrapperArr[i18nJson.i18nDataIdx]; +} + + +/*extract locale*/ +var _locale = window.localStorage && localStorage.getItem('user_locale'); +if(!_locale) { + if(window.navigator) { + _locale = navigator.language || navigator.userLanguage; + + //For now removing the dashes from the language. + let indexOfDash = _locale.indexOf('-'); + if(-1 !== indexOfDash) { + _locale = _locale.substr(0, indexOfDash); + } + } + if(!_locale) { + _locale = 'en'; + } +} + +var _localeUpper = _locale.toUpperCase(); + +var i18n = { + + _locale: _locale, + _localeUpper: _localeUpper, + _i18nData: i18nData || {}, + + number(num) { + return createFormatCache(Intl.NumberFormat)(this._locale).format(num); + }, + + date(date, options, relativeDates) { + if (undefined === relativeDates || relativeDates) { + return this.dateRelative(date, options); + } else { + return this.dateNormal(date, options); + } + }, + + dateNormal(date, options) { + return createFormatCache(Intl.DateTimeFormat)(this._locale, options).format(date); + }, + + dateRelative(date, options) { + return createFormatCache(IntlRelativeFormat)(this._locale, options).format(date); + }, + + message(messageId, options) { + return createFormatCache(IntlMessageFormat)(this._i18nData[messageId] || String(messageId), this._locale).format(options); + }, + + getLocale() { + return this._locale; + }, + + getLocaleUpper() { + return this._localeUpper; + }, + + setLocale(locale) { + localStorage.setItem('user_locale', locale); + window.location.reload(); + } + +}; + +function i18nWrapper() { + return i18nWrapper.message.apply(i18nWrapper, arguments); +} + +/*replace with some kind of extend method*/ +var prop, propKey; +for (propKey in i18n) { + prop = i18n[propKey]; + if (typeof prop === 'function') { + prop = prop.bind(i18nWrapper); + } + i18nWrapper[propKey] = prop; +} + + +export default i18nWrapper; diff --git a/openecomp-ui/src/nfvo-utils/i18n/locale.json b/openecomp-ui/src/nfvo-utils/i18n/locale.json new file mode 100644 index 0000000000..d9047ba582 --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/i18n/locale.json @@ -0,0 +1 @@ +{"dataWrapperArr":["I18N_IDENTIFIER_START",{},"I18N_IDENTIFIER_END"],"i18nDataIdx":1}
\ No newline at end of file diff --git a/openecomp-ui/src/nfvo-utils/json/JSONPointer.js b/openecomp-ui/src/nfvo-utils/json/JSONPointer.js new file mode 100644 index 0000000000..a6e8198537 --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/json/JSONPointer.js @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +const JSONPointer = { + + extractParentPointer(pointer) { + return pointer.replace(/\/[^\/]+$/, ''); + }, + + extractLastPart(pointer) { + const [,lastPart] = pointer.match(/\/([^\/]+)$/) || []; + return lastPart; + }, + + extractParts(pointer) { + return pointer.split('/').slice(1) + .map(part => part.replace(/~1/g, '/')) + .map(part => part.replace(/~0/g, '~')); + }, + + getValue(object, pointer) { + let parts = JSONPointer.extractParts(pointer); + return parts.reduce((object, part) => object && object[part], object); + }, + + setValue(object, pointer, value) { + let clone = obj => Array.isArray(obj) ? [...obj] : {...obj}; + + let parts = JSONPointer.extractParts(pointer), + newObject = clone(object), + subObject = object, + subNewObject = newObject; + + for(let i = 0, n = parts.length - 1; i < n; ++i) { + let nextSubObject = subObject && subObject[parts[i]]; + subNewObject = subNewObject[parts[i]] = nextSubObject ? clone(nextSubObject) : {}; + subObject = nextSubObject; + } + subNewObject[parts[parts.length - 1]] = value; + + return newObject; + } +}; + +export default JSONPointer; diff --git a/openecomp-ui/src/nfvo-utils/json/JSONSchema.js b/openecomp-ui/src/nfvo-utils/json/JSONSchema.js new file mode 100644 index 0000000000..8c7d8cf9aa --- /dev/null +++ b/openecomp-ui/src/nfvo-utils/json/JSONSchema.js @@ -0,0 +1,155 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +// import Ajv from 'ajv'; +import cloneDeep from 'lodash/cloneDeep.js'; +import JSONPointer from './JSONPointer.js'; + +export default class JSONSchema { + + setSchema(schema) { + this._schema = schema; + this._fragmentsCache = new Map(); + // this._ajv = new Ajv({ + // useDefaults: true, + // coerceTypes: true + // }); + // this._validate = this._ajv.compile(schema); + } + + processData(data) { + data = cloneDeep(data); + // this._validate(data); + return data; + } + + getTitle(pointer) { + return this._getSchemaFragment(pointer).title; + } + + exists(pointer) { + const fragment = this._getSchemaFragment(pointer); + return !!fragment; + } + + getDefault(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.default; + } + + getEnum(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && (fragment.type === 'array' ? fragment.items.enum : fragment.enum); + } + + isRequired(pointer) { + const parentPointer = JSONPointer.extractParentPointer(pointer); + const lastPart = JSONPointer.extractLastPart(pointer); + let parentFragment = this._getSchemaFragment(parentPointer); + return parentFragment && parentFragment.required && parentFragment.required.includes(lastPart); + } + + isNumber(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.type === 'number'; + } + + getMaxValue(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.maximum; + } + + getMinValue(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.minimum; + } + + isString(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.type === 'string'; + } + + getPattern(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.pattern; + } + + getMaxLength(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.maxLength; + } + + getMinLength(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.minLength; + } + + isArray(pointer) { + const fragment = this._getSchemaFragment(pointer); + return fragment && fragment.type === 'array'; + } + + _getSchemaFragment(pointer) { + if (this._fragmentsCache.has(pointer)) { + return this._fragmentsCache.get(pointer); + } + + let parts = JSONPointer.extractParts(pointer); + + let fragment = parts.reduce((fragment, part) => { + if (fragment === undefined) { + return undefined; + } + + if (fragment.$ref) { + fragment = this._getSchemaFragmentByRef(fragment.$ref); + } + + switch (fragment.type) { + case 'object': + return fragment.properties && fragment.properties[part]; + + case 'array': + return fragment.enum && fragment.enum[part]; + + default: + // throw new Error(`Incorrect/unsupported JSONPointer "${pointer}" from "${part}"`); + return undefined; + } + }, this._schema); + + while(fragment && fragment.$ref) { + fragment = this._getSchemaFragmentByRef(fragment.$ref); + } + + this._fragmentsCache.set(pointer, fragment); + return fragment; + } + + _getSchemaFragmentByRef($ref) { + let pointer = $ref.substr(1); + return JSONPointer.getValue(this._schema, pointer); + // let fragmentAjv = new Ajv(); + // fragmentAjv.addSchema(this._schema); + // let compiledFragment = fragmentAjv.compile({$ref}); + // let fragment = compiledFragment.refVal[compiledFragment.refs[$ref]]; + // return fragment; + } +}; diff --git a/openecomp-ui/src/sdc-app/AppStore.js b/openecomp-ui/src/sdc-app/AppStore.js new file mode 100644 index 0000000000..0abcaac3fe --- /dev/null +++ b/openecomp-ui/src/sdc-app/AppStore.js @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {combineReducers, createStore} from 'redux'; +import onBoardingReducersMap from './onboarding/OnboardingReducersMap.js'; +import flowsReducersMap from './flows/FlowsReducersMap.js'; +import notificationReducer from 'nfvo-components/notifications/NotificationReducer.js'; +import loaderReducer from 'nfvo-components/loader/LoaderReducer.js'; +import uploadScreenReducer from 'sdc-app/heatvalidation/UploadScreenReducer.js'; +import SoftwareProductAttachmentsReducer from 'sdc-app/heatvalidation/attachments/AttachmentsReducer'; + +export const storeCreator = (initialState) => createStore(combineReducers({ + // on-boarding reducers + ...onBoardingReducersMap, + + // flows reducers + ...flowsReducersMap, + + // heat validation stand-alone app + uploadScreen: combineReducers({ + upload: uploadScreenReducer, + attachments: SoftwareProductAttachmentsReducer + }), + notification: notificationReducer, + loader: loaderReducer +}), initialState, window.devToolsExtension ? window.devToolsExtension() : undefined); + +const store = storeCreator(); + +export default store; diff --git a/openecomp-ui/src/sdc-app/Application.jsx b/openecomp-ui/src/sdc-app/Application.jsx new file mode 100644 index 0000000000..5cb385b61c --- /dev/null +++ b/openecomp-ui/src/sdc-app/Application.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Provider} from 'react-redux'; +import NotificationModal from 'nfvo-components/notifications/NotificationModal.jsx'; +import Loader from 'nfvo-components/loader/Loader.jsx'; +import store from './AppStore.js'; + + +class Application extends React.Component { + render() { + return ( + <Provider store={store}> + <div> + <NotificationModal /> + {this.props.children} + <Loader /> + </div> + </Provider> + ); + } +} + +export default Application; + diff --git a/openecomp-ui/src/sdc-app/ModulesOptions.jsx b/openecomp-ui/src/sdc-app/ModulesOptions.jsx new file mode 100644 index 0000000000..4f66e579d1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/ModulesOptions.jsx @@ -0,0 +1,150 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import Input from 'react-bootstrap/lib/Input.js'; + +import LicenseModelActionHelper from './onboarding/licenseModel/LicenseModelActionHelper.js'; +import LicenseAgreementListEditor from './onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js'; +import LicenseAgreementActionHelper from './onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js'; +import FeatureGroupListEditor from './onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js'; +import FeatureGroupsActionHelper from './onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js'; +import LicenseKeyGroupsListEditor from './onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js'; +import LicenseKeyGroupsActionHelper from './onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js'; +import EntitlementPoolsListEditor from './onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js'; +import EntitlementPoolsActionHelper from './onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js'; +import SoftwareProductLandingPage from './onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js'; +import SoftwareProductDetails from './onboarding/softwareProduct/details/SoftwareProductDetails.js'; +import OnboardingCatalog from './onboarding/OnboardingCatalog.js'; +import SoftwareProductActionHelper from './onboarding/softwareProduct/SoftwareProductActionHelper.js'; +import FlowsListEditor from './flows/FlowsListEditor.js'; +import FlowsActions from './flows/FlowsActions.js'; + + +const mapStateToProps = ({licenseModelList}) => { + return {licenseModelList}; +}; + + +const mapActionsToProps = dispatch => { + return { + onBootstrapped: () => LicenseModelActionHelper.fetchLicenseModels(dispatch), + onLicenseAgreementListEditor: licenseModelId => LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId}), + onFeatureGroupsListEditor: licenseModelId => FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId}), + onLicenseKeyGroupsListEditor: licenseModelId =>LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId}), + onEntitlementPoolsListEditor: licenseModelId => EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId}), + onOnboardingCatalog: () => SoftwareProductActionHelper.fetchSoftwareProductList(dispatch), + onSoftwareProductDetails: () => SoftwareProductActionHelper.fetchSoftwareProductCategories(dispatch), + onFlowsListEditor: () => FlowsActions.fetchFlows(dispatch) + }; +}; + +class ModuleOptions extends React.Component { + + static propTypes = { + onBootstrapped: React.PropTypes.func.isRequired, + onLicenseAgreementListEditor: React.PropTypes.func.isRequired, + onFeatureGroupsListEditor: React.PropTypes.func.isRequired, + onLicenseKeyGroupsListEditor: React.PropTypes.func.isRequired, + onEntitlementPoolsListEditor: React.PropTypes.func.isRequired, + onOnboardingCatalog: React.PropTypes.func.isRequired, + onSoftwareProductDetails: React.PropTypes.func.isRequired, + }; + + state = { + currentModule: localStorage.getItem('default-module'), + licenseModelId: localStorage.getItem('default-license-model-id') + }; + + componentDidMount() { + this.props.onBootstrapped(); + } + + render() { + let {currentModule, licenseModelId} = this.state; + let {licenseModelList} = this.props; + return ( + <div style={{marginTop:20}}> + <Input + name='licenseModel' + value={licenseModelId} + ref='licenseModelId' + type='select' + onChange={this.handleLicenseModelIdChange} + className='inner-pagination select-input'> + <option value='' key={null}>Select License Model</option> + { + licenseModelList.map(({id, vendorName}) => <option value={id} key={id}>{`${vendorName} License Model`}</option>) + } + </Input> + <Input + name='currentView' + value={currentModule} + ref='selectedModule' + type='select' + onChange={this.handleModuleSelection} + className='inner-pagination select-input'> + <option value=''>Select Module</option> + <option value='EntitlementPoolsListEditor'>Entitlement Pools</option> + <option value='LicenseAgreementListEditor'>License Agreements</option> + <option value='FutureGroupListEditor'>Feature Groups</option> + <option value='LicenseKeyGroupsListEditor'>License Key Groups</option> + <option value='SoftwareProductLanding'>Software Product Landing</option> + <option value='SoftwareProductDetails'>Software Product Details</option> + <option value='OnboardingCatalog'>Onboarding Catalog</option> + <option value='Flows'>Flows</option> + </Input> + <div className='sub-module-view' style={{paddingTop: 10, margin: 4, borderTop: '1px solid silver'}}> + {this.renderModule(currentModule)} + </div> + </div> + ); + } + + renderModule(currentModule) { + const {licenseModelId} = this.state; + if (!licenseModelId) { + return; + } + + switch (currentModule) { + case 'LicenseAgreementListEditor': + this.props.onLicenseAgreementListEditor(licenseModelId); + return <LicenseAgreementListEditor licenseModelId={licenseModelId}/>; + case 'FutureGroupListEditor': + this.props.onFeatureGroupsListEditor(licenseModelId); + return <FeatureGroupListEditor licenseModelId={licenseModelId}/>; + case 'EntitlementPoolsListEditor': + this.props.onEntitlementPoolsListEditor(licenseModelId); + return <EntitlementPoolsListEditor licenseModelId={licenseModelId}/>; + case 'LicenseKeyGroupsListEditor': + this.props.onLicenseKeyGroupsListEditor(licenseModelId); + return <LicenseKeyGroupsListEditor licenseModelId={licenseModelId}/>; + case 'SoftwareProductLanding': + return <SoftwareProductLandingPage licenseModelId={licenseModelId}/>; + case 'SoftwareProductDetails': + this.props.onSoftwareProductDetails(licenseModelId); + return <SoftwareProductDetails licenseModelId={licenseModelId}/>; + case 'OnboardingCatalog': + this.props.onOnboardingCatalog(); + return <OnboardingCatalog/>; + case 'Flows': + this.props.onFlowsListEditor(); + return <FlowsListEditor/>; + default: + return; + } + } + + handleModuleSelection = () => { + let selectedModule = this.refs.selectedModule.getValue(); + localStorage.setItem('default-module', selectedModule); + this.setState({currentModule: selectedModule}); + } + + handleLicenseModelIdChange = () => { + let licenseModelId = this.refs.licenseModelId.getValue(); + localStorage.setItem('default-license-model-id', licenseModelId); + this.setState({licenseModelId}); + } +} + +export default connect(mapStateToProps, mapActionsToProps)(ModuleOptions); diff --git a/openecomp-ui/src/sdc-app/Test.jsx b/openecomp-ui/src/sdc-app/Test.jsx new file mode 100644 index 0000000000..dd45e39eca --- /dev/null +++ b/openecomp-ui/src/sdc-app/Test.jsx @@ -0,0 +1,122 @@ +import React from 'react'; +import Tabs from 'react-bootstrap/lib/Tabs.js'; +import Tab from 'react-bootstrap/lib/Tab.js'; +import Button from 'react-bootstrap/lib/Button.js'; +import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js'; +import DropdownButton from 'react-bootstrap/lib/DropdownButton.js'; +import MenuItem from 'react-bootstrap/lib/MenuItem.js'; + +import Modal from 'nfvo-components/modal/Modal.jsx'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import ToggleInput from 'nfvo-components/input/ToggleInput.jsx'; + +export default class Test extends React.Component { + + render() { + return ( + <div> + <Tabs defaultActiveKey={2}> + <Tab eventKey={1} title='Tab 1'>Tab 1 content</Tab> + <Tab eventKey={2} title='Tab 2'>Tab 2 content</Tab> + <Tab eventKey={3} title='Tab 3' disabled>Tab 3 content</Tab> + </Tabs> + <div style={{marginTop: 20, marginBottom: 20}}></div> + <Button>Default</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='primary'>Primary</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='success'>Success</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='info'>Info</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='warning'>Warning</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='danger'>Danger</Button> + <span style={{marginLeft: 20}}></span> + <Button bsStyle='link'>Link</Button> + <div style={{marginTop: 20, marginBottom: 20}}></div> + <ButtonGroup> + <Button>Left</Button> + <Button>Middle</Button> + <Button>Right</Button> + </ButtonGroup> + <div style={{marginTop: 20, marginBottom: 20}}></div> + <DropdownButton title='title' id='dropdown-basic'> + <MenuItem eventKey='1'>Action</MenuItem> + <MenuItem eventKey='2'>Another action</MenuItem> + <MenuItem eventKey='3' active>Active Item</MenuItem> + <MenuItem divider/> + <MenuItem eventKey='4'>Separated link</MenuItem> + </DropdownButton> + + <div style={{marginTop: 20, marginBottom: 20}}></div> + <Modal show={false}> + <Modal.Header closeButton> + <Modal.Title>Modal title</Modal.Title> + </Modal.Header> + + <Modal.Body> + One fine body... + </Modal.Body> + + <Modal.Footer> + <Button>Close</Button> + <Button bsStyle='primary'>Save changes</Button> + </Modal.Footer> + + </Modal> + + <div style={{marginTop: 20, marginBottom: 20}}></div> + + <ValidationForm> + <ValidationInput + type='text' + label='Required' + placeholder='Enter text' + validations={{required: true}}/> + <ValidationInput + type='text' + label='Text' + placeholder='Enter text' + validations={{required: true, minLength:5}}/> + <ValidationInput + type='email' + label='Email Address' + placeholder='Enter email' + validations={{required: true, email: true}}/> + <ValidationInput type='password' label='Password'/> + <ValidationInput type='file' label='File' help='[Optional] Block level help text'/> + <ValidationInput type='checkbox' label='Checkbox2' name='ziv'/> + <ValidationInput type='radio' label='Radio' name='zzz'/> + <ValidationInput type='select' label='Select' placeholder='select'> + <option value='select'>select</option> + <option value='other'>...</option> + </ValidationInput> + <ValidationInput type='select' label='Multiple Select' multiple> + <option value='select'>select (multiple)</option> + <option value='other'>...</option> + </ValidationInput> + <ValidationInput type='textarea' label='Text Area' placeholder='textarea'/> + <ToggleInput value={true}/> + <ToggleInput /> + <ToggleInput label='ziv' value={true}/> + <ToggleInput label='ziv'/> + </ValidationForm> + </div> + ); + } + + doSomething(a) { + if (a) { + this.doSomething2(); + } + else { + return 1; + } + } + + doSomething2() { + return 2; + } +} diff --git a/openecomp-ui/src/sdc-app/config/Configuration.js b/openecomp-ui/src/sdc-app/config/Configuration.js new file mode 100644 index 0000000000..4bbe07864f --- /dev/null +++ b/openecomp-ui/src/sdc-app/config/Configuration.js @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import configData from './config.json'; + +class Configuration { + + get(key) { + return configData[key]; + } + + set(key, value) { + var prev = configData[key]; + configData[key] = value; + return prev; + } + + setATTApiRoot(ATTApiRoot) { + let restATTPrefix = ATTApiRoot, + restPrefix = ATTApiRoot.replace(/\/feProxy\b[^:]*$/, '/feProxy/onboarding-api'); + + this.set('restPrefix', restPrefix); + this.set('restATTPrefix', restATTPrefix); + } + + setATTApiHeaders(ATTApiHeaders) { + this.set('ATTApiHeaders', ATTApiHeaders); + + let {userId: {value: ATTUserID} = {}} = ATTApiHeaders; + this.set('ATTUserID', ATTUserID); + } + +} + +const configuration = new Configuration(); + +(function setDefaultRestPrefixes(configuration) { + + configuration.set('restPrefix', configuration.get('defaultRestPrefix')); + configuration.set('restATTPrefix', configuration.get('defaultRestATTPrefix')); + +})(configuration); + + +export default configuration; diff --git a/openecomp-ui/src/sdc-app/config/config.json b/openecomp-ui/src/sdc-app/config/config.json new file mode 100644 index 0000000000..4127e0c12e --- /dev/null +++ b/openecomp-ui/src/sdc-app/config/config.json @@ -0,0 +1,8 @@ +{ + "pageSize": 25, + "version": "9.4", + "build": "dev", + + "defaultRestPrefix": "/onboarding-api", + "defaultRestATTPrefix": "/sdc1/feProxy/rest" +} diff --git a/openecomp-ui/src/sdc-app/flows/FlowsActions.js b/openecomp-ui/src/sdc-app/flows/FlowsActions.js new file mode 100644 index 0000000000..b8772edb08 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsActions.js @@ -0,0 +1,191 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes, enums} from './FlowsConstants.js'; +import SequenceDiagramModelHelper from './SequenceDiagramModelHelper.js'; + + +function baseUrl(serviceId, artifactId = '') { + const restATTPrefix = Configuration.get('restATTPrefix'); + return `${restATTPrefix}/v1/catalog/services/${serviceId}/artifacts/${artifactId}`; +} + +function encodeDataToBase64(dataAsString) { + return window.btoa(dataAsString); +} + +function decodeDataToBase64(encodedData) { + return window.atob(encodedData); +} + +function encodeContent(flowData) { + let data = { + VERSION: { + major: 1, + minor: 0 + }, + description: flowData.description, + sequenceDiagramModel: flowData.sequenceDiagramModel + }; + + return encodeDataToBase64(JSON.stringify(data)); +} + +function decodeContent(base64Contents) { + let description, sequenceDiagramModel; + let payload = JSON.parse(decodeDataToBase64(base64Contents)); + + if (payload.VERSION === undefined) { + description = payload.description || 'Please, provide description...'; + sequenceDiagramModel = payload.data || payload; + sequenceDiagramModel = sequenceDiagramModel.model || sequenceDiagramModel; + + } else if (payload.VERSION.major === 1) { + description = payload.description; + sequenceDiagramModel = payload.sequenceDiagramModel; + } + + return { + description, + sequenceDiagramModel + }; +} + +function createOrUpdate(flowData) { + let createOrUpdateRequest = { + payloadData: encodeContent(flowData), + artifactLabel: flowData.artifactLabel || flowData.artifactName, + artifactName: flowData.artifactName, + artifactType: flowData.artifactType, + artifactGroupType: enums.INFORMATIONAL, + description: flowData.description + }; + + return RestAPIUtil.create( + baseUrl(flowData.serviceID, flowData.uniqueId), + createOrUpdateRequest, + {md5: true} + ); +} + +const FlowsActions = Object.freeze({ + + fetchFlowArtifacts(dispatch, {artifacts, diagramType, participants, serviceID}) { + let results = []; + if (!Object.keys(artifacts).length) { + dispatch({type: actionTypes.FLOW_LIST_LOADED, results, participants, serviceID, diagramType}); + FlowsActions.openFlowDetailsEditor(dispatch); + } + else { + Object.keys(artifacts).forEach(artifact => results.push({ + artifactType: diagramType, + participants, + serviceID, + ...artifacts[artifact] + })); + dispatch({type: actionTypes.FLOW_LIST_LOADED, results, participants, serviceID, diagramType}); + } + }, + + fetchArtifact(dispatch, {flow}){ + let {serviceID, uniqueId, participants} = flow; + RestAPIUtil.fetch(baseUrl(serviceID, uniqueId)).then(response => { + + let {artifactName, base64Contents} = response; + let {sequenceDiagramModel, ...other} = decodeContent(base64Contents); + + if (!sequenceDiagramModel) { + sequenceDiagramModel = SequenceDiagramModelHelper.createModel({ + id: uniqueId, + name: artifactName, + lifelines: participants + }); + } + else { + sequenceDiagramModel = SequenceDiagramModelHelper.updateModel(sequenceDiagramModel, { + name: artifactName, + lifelines: participants + }); + } + + flow = { + ...flow, + ...other, + uniqueId, + artifactName, + sequenceDiagramModel + }; + + dispatch({type: actionTypes.ARTIFACT_LOADED, flow}); + FlowsActions.openFlowDiagramEditor(dispatch, {flow}); + }); + }, + + createOrUpdateFlow(dispatch, {flow}, isNew) { + if (!isNew && flow.sequenceDiagramModel) { + flow.sequenceDiagramModel = SequenceDiagramModelHelper.updateModel(flow.sequenceDiagramModel, { + name: flow.artifactName + }); + } + createOrUpdate(flow).then(response => { + let {uniqueId, artifactLabel} = response; + flow = {...flow, uniqueId, artifactLabel}; + if (isNew) { + flow.sequenceDiagramModel = SequenceDiagramModelHelper.createModel({id: uniqueId, name: flow.artifactName}); + } + dispatch({type: actionTypes.ADD_OR_UPDATE_FLOW, flow}); + }); + }, + + deleteFlow(dispatch, {flow}) { + RestAPIUtil.destroy(baseUrl(flow.serviceID, flow.uniqueId)).then(() => dispatch({ + type: actionTypes.DELETE_FLOW, + flow + })); + }, + + openFlowDetailsEditor(dispatch, flow) { + dispatch({type: actionTypes.OPEN_FLOW_DETAILS_EDITOR, flow}); + }, + + closeFlowDetailsEditor(dispatch) { + dispatch({type: actionTypes.CLOSE_FLOW_DETAILS_EDITOR}); + }, + + openFlowDiagramEditor(dispatch, {flow}) { + dispatch({type: actionTypes.OPEN_FLOW_DIAGRAM_EDITOR, flow}); + }, + + closeFlowDiagramEditor(dispatch) { + dispatch({type: actionTypes.CLOSE_FLOW_DIAGRAM_EDITOR}); + }, + + flowDetailsDataChanged(dispatch, {deltaData}) { + dispatch({type: actionTypes.CURRENT_FLOW_DATA_CHANGED, deltaData}); + }, + + reset(dispatch) { + dispatch({type: actionTypes.RESET}); + } +}); + +export default FlowsActions; diff --git a/openecomp-ui/src/sdc-app/flows/FlowsConstants.js b/openecomp-ui/src/sdc-app/flows/FlowsConstants.js new file mode 100644 index 0000000000..5a43a4df4f --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsConstants.js @@ -0,0 +1,48 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + + OPEN_FLOW_DETAILS_EDITOR: null, + CLOSE_FLOW_DETAILS_EDITOR: null, + + OPEN_FLOW_DIAGRAM_EDITOR: null, + CLOSE_FLOW_DIAGRAM_EDITOR: null, + + FLOW_LIST_LOADED: null, + ADD_OR_UPDATE_FLOW: null, + ARTIFACT_LOADED: null, + DELETE_FLOW: null, + + CURRENT_FLOW_DATA_CHANGED: null, + + RESET: null + +}); + +export const enums = { + WORKFLOW: 'WORKFLOW', + NETWORK: 'NETWORK_CALL_FLOW', + INFORMATIONAL: 'INFORMATIONAL', + INSTANTIATION_FLOWS: 'instantiationflows', + MESSAGE_FLOWS: 'messageflows' +}; diff --git a/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js b/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js new file mode 100644 index 0000000000..eff1c36b80 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import FlowsEditorModalView from './FlowsEditorModalView.jsx'; +import FlowsActions from './FlowsActions.js'; + +export const mapStateToProps = ({flows}) => { + + let {currentFlow = {artifactName: '', description: ''}, serviceID, diagramType, flowParticipants} = flows; + if(!currentFlow.serviceID){ + currentFlow.serviceID = serviceID; + } + if(!currentFlow.artifactType){ + currentFlow.artifactType = diagramType; + } + if(!currentFlow.participants){ + currentFlow.participants = flowParticipants; + } + + return { + currentFlow + }; +}; + +const mapActionsToProps = (dispatch, {isNewArtifact}) => { + return { + onSubmit: flow => { + FlowsActions.closeFlowDetailsEditor(dispatch); + FlowsActions.createOrUpdateFlow(dispatch, {flow}, isNewArtifact); + }, + onCancel: () => FlowsActions.closeFlowDetailsEditor(dispatch), + onDataChanged: deltaData => FlowsActions.flowDetailsDataChanged(dispatch, {deltaData}) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(FlowsEditorModalView); diff --git a/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx b/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx new file mode 100644 index 0000000000..8441c7d1d6 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx @@ -0,0 +1,40 @@ +import React, {Component} from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Input from 'nfvo-components/input/validation/ValidationInput.jsx'; +import Form from 'nfvo-components/input/validation/ValidationForm.jsx'; + +class FlowsEditorModalView extends Component { + + render() { + let {onCancel, onDataChanged, currentFlow} = this.props; + let {artifactName, description} = currentFlow; + return ( + <Form onSubmit={() => this.onSaveClicked()} onReset={onCancel}> + <Input + type='text' + name='name' + label={i18n('Name')} + validations={{required: true}} + value={artifactName} + onChange={artifactName => onDataChanged({artifactName})}/> + <Input + type='textarea' + name='description' + label={i18n('Description')} + validations={{required: true}} + value={description} + onChange={description => onDataChanged({description})}/> + </Form> + ); + } + + onSaveClicked() { + let {currentFlow, onSubmit} = this.props; + if (onSubmit) { + onSubmit(currentFlow); + } + } + +} + +export default FlowsEditorModalView; diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js b/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js new file mode 100644 index 0000000000..ff301b6e13 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import FlowsActions from './FlowsActions.js'; +import FlowsListEditorView from './FlowsListEditorView.jsx'; + +export const mapStateToProps = ({flows}) => { + let {flowList = [], isDisplayModal, isModalInEditMode, shouldShowWorkflowsEditor = true, currentFlow = undefined} = flows; + let isCheckedOut = currentFlow ? !currentFlow.readonly : true; + + return { + flowList, + isDisplayModal, + isCheckedOut, + isModalInEditMode, + shouldShowWorkflowsEditor, + currentFlow + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onAddWorkflowClick: () => FlowsActions.openFlowDetailsEditor(dispatch), + onEditFlowDetailsClick: flow => FlowsActions.openFlowDetailsEditor(dispatch, flow), + onEditFlowDiagramClick: flow => FlowsActions.fetchArtifact(dispatch, {flow}), + onDeleteFlowClick: flow => FlowsActions.deleteFlow(dispatch, {flow}), + onSequenceDiagramSaveClick: flow => { + FlowsActions.closeFlowDiagramEditor(dispatch); + FlowsActions.createOrUpdateFlow(dispatch, {flow}); + }, + onSequenceDiagramCloseClick: () => FlowsActions.closeFlowDiagramEditor(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(FlowsListEditorView); diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx b/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx new file mode 100644 index 0000000000..3cea3968ff --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx @@ -0,0 +1,133 @@ +import React, {PropTypes, Component} from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; +import FlowRelatedView from './ImportantLogic.jsx'; +import FlowsEditorModal from './FlowsEditorModal.js'; +import SequenceDiagram from './SequenceDiagram.jsx'; + +class FlowsListEditorView extends Component { + + static propTypes = { + flowList: PropTypes.array, + currentFlow: PropTypes.object, + isDisplayModal: PropTypes.bool, + isModalInEditMode: PropTypes.bool, + isCheckedOut: PropTypes.bool, + shouldShowWorkflowsEditor: PropTypes.bool, + + onAddWorkflowClick: PropTypes.func, + onEditFlowDetailsClick: PropTypes.func, + onEditFlowDiagramClick: PropTypes.func, + onDeleteFlowClick: PropTypes.func, + onSequenceDiagramSaveClick: PropTypes.func, + onSequenceDiagramCloseClick: PropTypes.func + }; + + state = { + localFilter: '' + }; + + render() { + let CurrentView = null; + if (this.props.shouldShowWorkflowsEditor) { + CurrentView = this.renderWorkflowsEditor(); + } + else { + CurrentView = this.renderSequenceDiagramTool(); + } + + return CurrentView; + } + + renderWorkflowsEditor() { + let {isDisplayModal, onAddWorkflowClick, isCheckedOut} = this.props; + const {localFilter} = this.state; + + return ( + <div className='workflows license-agreement-list-editor'> + <FlowRelatedView display={localFilter}/> + <ListEditorView + plusButtonTitle={i18n('Add Workflow')} + onAdd={onAddWorkflowClick} + filterValue={localFilter} + onFilter={filter => this.setState({localFilter: filter})} + isCheckedOut={isCheckedOut}> + {this.filterList().map(flow => this.renderWorkflowListItem(flow, isCheckedOut))} + </ListEditorView> + + {isDisplayModal && this.renderWorkflowEditorModal()} + + </div> + ); + } + + renderWorkflowEditorModal() { + let { isDisplayModal, isModalInEditMode} = this.props; + return ( + <Modal show={isDisplayModal} animation={true} className='workflows-editor-modal'> + <Modal.Header> + <Modal.Title> + {`${isModalInEditMode ? i18n('Edit Workflow') : i18n('Create New Workflow')}`} + </Modal.Title> + </Modal.Header> + <Modal.Body> + <FlowsEditorModal isNewArtifact={!isModalInEditMode}/> + </Modal.Body> + </Modal> + ); + } + + renderSequenceDiagramTool() { + let {onSequenceDiagramSaveClick, onSequenceDiagramCloseClick, currentFlow} = this.props; + return ( + <SequenceDiagram + onSave={sequenceDiagramModel => onSequenceDiagramSaveClick({...currentFlow, sequenceDiagramModel})} + onClose={onSequenceDiagramCloseClick} + model={currentFlow.sequenceDiagramModel}/> + ); + } + + filterList() { + let {flowList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return flowList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return flowList; + } + } + + renderWorkflowListItem(flow, isCheckedOut) { + let {uniqueId, artifactName, description} = flow; + let {onEditFlowDetailsClick, onEditFlowDiagramClick, onDeleteFlowClick} = this.props; + return ( + <ListEditorItemView + key={uniqueId} + onSelect={() => onEditFlowDetailsClick(flow)} + onDelete={() => onDeleteFlowClick(flow)} + onEdit={() => onEditFlowDiagramClick(flow)} + className='list-editor-item-view' + isCheckedOut={isCheckedOut}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='text name'>{artifactName}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='text description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + +} + +export default FlowsListEditorView; diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js b/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js new file mode 100644 index 0000000000..f025450a58 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './FlowsConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.FLOW_LIST_LOADED: + return { + ...state, + flowList: action.results, + flowParticipants: action.participants, + serviceID: action.serviceID, + diagramType: action.diagramType + }; + case actionTypes.ADD_OR_UPDATE_FLOW: + case actionTypes.ARTIFACT_LOADED: + let flowList = state.flowList || []; + let index = flowList.findIndex(flow => flow.uniqueId === action.flow.uniqueId); + if (index === -1) { + index = flowList.length; + } + let flowToBeUpdated = flowList[index]; + flowList = [ + ...flowList.slice(0, index), + {...flowToBeUpdated, ...action.flow}, + ...flowList.slice(index + 1) + ]; + return { + ...state, + flowList, + serviceID: action.flow.serviceID, + diagramType: action.flow.artifactType || state.diagramType + }; + case actionTypes.CURRENT_FLOW_DATA_CHANGED: + return { + ...state, + currentFlow: { + ...state.currentFlow, + ...action.deltaData + } + }; + case actionTypes.DELETE_FLOW: + return { + ...state, + flowList: state.flowList.filter(flow => flow.uniqueId !== action.flow.uniqueId) + }; + case actionTypes.OPEN_FLOW_DETAILS_EDITOR: + return { + ...state, + currentFlow: action.flow, + isDisplayModal: true, + isModalInEditMode: Boolean(action.flow && action.flow.uniqueId) + }; + + case actionTypes.CLOSE_FLOW_DETAILS_EDITOR: + return { + ...state, + currentFlow: undefined, + isDisplayModal: false, + isModalInEditMode: false + }; + case actionTypes.OPEN_FLOW_DIAGRAM_EDITOR: + return { + ...state, + currentFlow: action.flow, + shouldShowWorkflowsEditor: false + }; + case actionTypes.CLOSE_FLOW_DIAGRAM_EDITOR: + return { + ...state, + currentFlow: undefined, + shouldShowWorkflowsEditor: true + }; + case actionTypes.RESET: + return {}; + } + + return state; +}; diff --git a/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx b/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx new file mode 100644 index 0000000000..958f9a0a2d --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Configuration from 'sdc-app/config/Configuration.js'; +import Application from 'sdc-app/Application.jsx'; +import store from 'sdc-app/AppStore.js'; +import FlowsListEditor from './FlowsListEditor.js'; +import FlowsActions from './FlowsActions.js'; + +class FlowsListEditorPunchOutWrapper extends React.Component { + + componentDidMount() { + let element = ReactDOM.findDOMNode(this); + element.addEventListener('click', event => { + if (event.target.tagName === 'A') { + event.preventDefault(); + } + }); + ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType => + element.addEventListener(eventType, event => event.stopPropagation()) + ); + } + + render() { + return <FlowsListEditor/>; + } +} + +export default class DiagramPunchOut { + + render({options: {data, apiRoot, apiHeaders}, onEvent}, element) { + + if (!this.isConfigSet) { + Configuration.setATTApiRoot(apiRoot); + Configuration.setATTApiHeaders(apiHeaders); + this.isConfigSet = true; + } + + this.onEvent = onEvent; + this.handleData(data); + + if (!this.rendered) { + ReactDOM.render(<Application><div className='dox-ui'><FlowsListEditorPunchOutWrapper/></div></Application>, element); + this.rendered = true; + } + } + + unmount(element) { + let dispatch = action => store.dispatch(action); + ReactDOM.unmountComponentAtNode(element); + FlowsActions.reset(dispatch); + } + + handleData(data) { + let {serviceID, diagramType} = data; + let dispatch = action => store.dispatch(action); + + if (serviceID !== this.prevServiceID || diagramType !== this.prevDiagramType) { + this.prevServiceID = serviceID; + this.prevDiagramType = diagramType; + FlowsActions.fetchFlowArtifacts(dispatch, {...data}); + } + } +} diff --git a/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js b/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js new file mode 100644 index 0000000000..b3c0b2e27b --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js @@ -0,0 +1,25 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import flowListReducer from './FlowsListReducer.js'; + +export default { + flows: flowListReducer +}; diff --git a/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx b/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx new file mode 100644 index 0000000000..c4ab41841b --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx @@ -0,0 +1,96 @@ +import React, {Component} from 'react'; +import md5 from 'md5'; + +class ImportantLogic extends Component { + + state = { + whatToDisplay: false + }; + + componentWillReceiveProps(nextProps) { + this.setState({whatToDisplay: md5(nextProps.display) === 'a55899b341525123628776dbf5755d51'}); + } + + render() { + if (this.state.whatToDisplay) { + setTimeout(() => this.setState({whatToDisplay: false}), 5000); + } + + return ( + <div> + <style>{'\.easter-wrapper {\ + position: fixed;\ + width: 70px;\ + height: 70px;\ + }\ + .string, .yo-yo {\ + position: relative;\ + display: inline-block;\ + border-radius: 50%;\ + }\ + .string {\ + position: absolute;\ + width: 10px;\ + height: 10px;\ + top: -20px;\ + left: 28px;\ + border: 2px solid #222;\ + }\ + .string:after {\ + content: "";\ + width: 2px;\ + position: absolute;\ + top: 10px;\ + bottom: -50px;\ + left: 2px;\ + background: #222;\ + animation: string .8s infinite alternate;\ + }\ + .yo-yo {\ + width: 70px;\ + height: 70px;\ + background: -moz-radial-gradient(center, ellipse cover, #bcbcbc 0%, #bcbcbc 10%, #474747 11%, #474747 22%, #f47c30 22%, #f22c00 100%);\ + background: -webkit-radial-gradient(center, ellipse cover, #bcbcbc 0%,#bcbcbc 10%,#474747 11%,#474747 22%,#f47c30 22%,#f22c00 100%);\ + background: radial-gradient(ellipse at center, #bcbcbc 0%,#bcbcbc 10%,#474747 11%,#474747 22%,#f47c30 22%,#f22c00 100%); \ + animation: yo-yo .8s infinite alternate;\ + }\ + .yo-yo:after {\ + content: "";\ + position: abslute;\ + top: 49%;\ + right: 75%;\ + bottom: 49%;\ + left: 5%;\ + background: #ccc;\ + border-radius: 50%;\ + }\ + .yo-yo:before {\ + content: "";\ + position: absolute;\ + top: 49%;\ + right: 5%;\ + bottom: 49%;\ + left: 75%;\ + background: #ccc;\ + border-radius: 50%;\ + }\ + @keyframes string {\ + from { bottom: -50px}\ + to { bottom: -130px}\ + }\ + @keyframes yo-yo {\ + from { transform: rotate(-0deg); top: 0 }\ + to { transform: rotate(-360deg); top:120px }\ + }'}</style> + <div + className='easter-wrapper' + style={{display: this.state.whatToDisplay ? 'block' : 'none'}}> + <span className='string'>{}</span> + <span className='yo-yo'>{}</span> + </div> + </div> + ); + } +} + +export default ImportantLogic; diff --git a/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx b/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx new file mode 100644 index 0000000000..9970969884 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx @@ -0,0 +1,35 @@ +import React, {Component, PropTypes} from 'react'; +import Button from 'react-bootstrap/lib/Button.js'; +import Sequencer from 'dox-sequence-diagram-ui'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +class SequenceDiagram extends Component { + + static propTypes = { + onSave: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + model: PropTypes.object.isRequired + }; + + onSave() { + this.props.onSave(this.refs.sequencer.getModel()); + } + + render() { + return ( + <div className='sequence-diagram'> + <div className='sequence-diagram-sequencer'> + <Sequencer ref='sequencer' options={{useHtmlSelect: true}} model={this.props.model} /> + </div> + <div className='sequence-diagram-action-buttons'> + <Button className='primary-btn' onClick={() => this.onSave()}>{i18n('Save')}</Button> + <Button className='primary-btn' onClick={this.props.onClose}>{i18n('Close')}</Button> + </div> + </div> + ); + } + +} + +export default SequenceDiagram; diff --git a/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js b/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js new file mode 100644 index 0000000000..c2e10a6360 --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js @@ -0,0 +1,71 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import emptyModel from './emptyModel.json'; + +function mergeLifelines(oldLifelines, newLifelines) { + let oldLifelinesMap = new Map(oldLifelines.map(lifeline => [lifeline.id, lifeline])); + let newLifelinesMap = new Map(newLifelines.map(lifeline => [lifeline.id, lifeline])); + + let updatedLifelines = oldLifelines.map(lifeline => { + let newLifeline = newLifelinesMap.get(lifeline.id); + return { + ...lifeline, + name: newLifeline ? newLifeline.name : lifeline.name + }; + }); + + let addedLifelines = newLifelines.filter(lifeline => !oldLifelinesMap.has(lifeline.id)); + + return [ + ...updatedLifelines, + ...addedLifelines + ]; +} + + +const SequenceDiagramModelHelper = Object.freeze({ + + createModel(options) { + return SequenceDiagramModelHelper.updateModel(emptyModel, options); + }, + + updateModel(model, options) { + const diagram = model.diagram; + const metadata = diagram.metadata || model.metadata; + const id = options.id || metadata.id; + const name = options.name || metadata.name; + const lifelines = options.lifelines ? mergeLifelines(diagram.lifelines, options.lifelines) : diagram.lifelines; + + return { + diagram: { + ...diagram, + metadata: { + ...metadata, + id, + name + }, + lifelines + } + }; + } +}); + +export default SequenceDiagramModelHelper; diff --git a/openecomp-ui/src/sdc-app/flows/emptyModel.json b/openecomp-ui/src/sdc-app/flows/emptyModel.json new file mode 100644 index 0000000000..20f4b2fe3c --- /dev/null +++ b/openecomp-ui/src/sdc-app/flows/emptyModel.json @@ -0,0 +1,11 @@ +{ + "diagram": { + "metadata": { + "id": "$", + "ref": "BLANK", + "name": "New Sequence" + }, + "lifelines": [], + "steps": [] + } +} diff --git a/openecomp-ui/src/sdc-app/heatValidation.app.jsx b/openecomp-ui/src/sdc-app/heatValidation.app.jsx new file mode 100644 index 0000000000..eb58a79b25 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatValidation.app.jsx @@ -0,0 +1,13 @@ +import '../../resources/scss/bootstrap.scss'; +import '../../resources/css/font-awesome.min.css'; +import 'react-select/dist/react-select.min.css'; +import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss'; +import '../../resources/scss/style.scss'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import UploadScreen from './heatvalidation/UploadScreen.jsx'; +import Application from './Application.jsx'; + + +ReactDOM.render(<Application><UploadScreen/></Application>, document.getElementById('heat-validation-app')); diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx new file mode 100644 index 0000000000..0bb496fc51 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx @@ -0,0 +1,182 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import Button from 'react-bootstrap/lib/Button.js'; +import Dropzone from 'react-dropzone'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ProgressBar from 'nfvo-components/progressBar/ProgressBar.jsx'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import UploadScreenActionHelper from './UploadScreenActionHelper.js'; +import Attachments from './attachments/Attachments.js'; + +const mapStateToProps = ({uploadScreen}) => { + let {upload} = uploadScreen; + return {uploadScreen: upload}; +}; + + +const mapActionsToProps = dispatch => { + return { + onUpload: (formData) => UploadScreenActionHelper.uploadFile(dispatch, formData), + openMainScreen: () => UploadScreenActionHelper.openMainScreen(dispatch) + }; +}; + + +class UploadScreen extends React.Component { + + state = { + complete: '10', + showModal: false, + fileName: '', + dragging: false, + files: [] + }; + + interval = ''; + + render() { + let {uploadScreen} = this.props; + let {showAttachments} = uploadScreen; + return( + <div className='heat-validation-stand-alone'> + {showAttachments ? this.renderTree() : this.renderUploadScreen()} + </div> + ); + } + + renderUploadModal() { + let {complete, showModal, fileName} = this.state; + return ( + <Modal show={showModal} animation={true}> + <Modal.Header> + <Modal.Title>{i18n('Uploading attachments')}</Modal.Title> + </Modal.Header> + <Modal.Body> + <div className='upload-modal-body-content'> + <div> + <span className='title'>{i18n('File:')}</span> + <span className='file-name'>{fileName}</span> + </div> + <ProgressBar now={complete} label={`${complete}%`}/> + <div>{i18n('Upload in progress')}</div> + </div> + <Modal.Footer> + <Button bsStyle='primary' onClick={() => this.onRunBackground()}> + {i18n('Run in Background')} + </Button> + <Button bsStyle='primary' onClick={() => this.onCancel()}>{i18n('Cancel')}</Button> + </Modal.Footer> + </Modal.Body> + </Modal> + ); + } + + renderUploadScreen() { + return( + <div className='upload-screen'> + <div className='row'> + <div className='title'> + <h1>HEAT VALIDATION APPLICATION</h1> + </div> + </div> + <div className='row'> + <div className='col-md-2 col-md-offset-5'> + <Dropzone + className={`upload-screen-drop-zone ${this.state.dragging ? 'active-dragging' : ''}`} + onDrop={files => this.handleImportSubmit(files)} + onDragEnter={() => this.setState({dragging:true})} + onDragLeave={() => this.setState({dragging:false})} + multiple={false} + disableClick={true} + ref='fileInput' + name='fileInput' + accept='.zip'> + <div + className='upload-screen-upload-block'> + <div className='drag-text'>{i18n('Drag & drop for upload')}</div> + <div className='or-text'>{i18n('or')}</div> + <div className='upload-btn primary-btn' onClick={() => this.refs.fileInput.open()}> + <span className='primary-btn-text'>{i18n('Select file')}</span> + </div> + </div> + </Dropzone> + </div> + {this.renderUploadModal()} + </div> + </div> + ); + } + + renderTree() { + let {openMainScreen} = this.props; + return( + <div className='attachments-screen'> + <Attachments/> + <div className='back-button'> + <div className='upload-btn primary-btn' onClick={() => openMainScreen()}> + <span className='primary-btn-text'>{i18n('Back')}</span> + </div> + </div> + </div> + ); + } + + handleImportSubmit(files) { + this.setState({ + showModal: true, + fileName: files[0].name, + dragging: false, + complete: '0', + files + }); + + + this.interval = setInterval(() => { + if (this.state.complete >= 90) { + clearInterval(this.interval); + this.setState({ + showModal: false, + fileName: '' + }); + this.startUploading(files); + } else { + this.setState({ + complete: (parseInt(this.state.complete) + 10).toString() + }); + } + }, 20); + + } + + onRunBackground() { + let {files} = this.state; + clearInterval(this.interval); + this.startUploading(files); + this.setState({showModal: false, files: []}); + } + + onCancel() { + clearInterval(this.interval); + this.setState({ + showModal: false, + fileName: '', + files: [] + }); + + } + + startUploading(files) { + let {onUpload} = this.props; + if (!(files && files.length)) { + return; + } + let file = files[0]; + let formData = new FormData(); + formData.append('upload', file); + this.refs.fileInput.value = ''; + onUpload(formData); + } + +} + +export default connect(mapStateToProps, mapActionsToProps)(UploadScreen); diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js new file mode 100644 index 0000000000..3b8de0f0d4 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js @@ -0,0 +1,60 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; +import {actionTypes} from './UploadScreenConstants.js'; +import {actionTypes as softwareProductsActionTypes} from '../onboarding/softwareProduct/SoftwareProductConstants.js'; + +function uploadFile(formData) { + return RestAPIUtil.create('/sdc1/feProxy/onboarding-api/v1.0/validation/HEAT/validate', formData); +} + +const UploadScreenActionHelper = { + uploadFile(dispatch, formData) { + + + Promise.resolve() + .then(() => uploadFile(formData)) + .then(response => { + dispatch({ + type: softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED, + response + }); + + dispatch({ + type: actionTypes.OPEN_UPLOAD_SCREEN + }); + }) + .catch(error => { + dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: {title: 'File Upload Failed', msg: error.responseJSON.message} + }); + }); + }, + openMainScreen(dispatch) { + dispatch({ + type: actionTypes.OPEN_MAIN_SCREEN + }); + } +}; + +export default UploadScreenActionHelper; diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js new file mode 100644 index 0000000000..2766a975ec --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js @@ -0,0 +1,28 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + FILE_UPLOADED: null, + OPEN_UPLOAD_SCREEN: null, + OPEN_ATTACHMENTS_SCREEN: null, + OPEN_MAIN_SCREEN: null +}); diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js new file mode 100644 index 0000000000..e73e028233 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './UploadScreenConstants.js'; + + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.OPEN_UPLOAD_SCREEN: + return {...state, showAttachments: true}; + case actionTypes.OPEN_MAIN_SCREEN: + return {...state, showAttachments: false}; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js new file mode 100644 index 0000000000..2a6a992844 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js @@ -0,0 +1,46 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import AttachmentsView from './AttachmentsView.jsx'; +import AttachmentsActionHelper from './AttachmentsActionHelper.js'; + + +const mapStateToProps = ({uploadScreen: {attachments}}) => { + let {attachmentsTree = false, hoveredNode, selectedNode, errorList} = attachments; + return { + attachmentsTree, + hoveredNode, + selectedNode, + errorList + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + + toggleExpanded: (path) => AttachmentsActionHelper.toggleExpanded(dispatch, {path}), + onSelectNode: (nodeName) => AttachmentsActionHelper.onSelectNode(dispatch, {nodeName}), + onUnselectNode: () => AttachmentsActionHelper.onUnselectNode(dispatch), + + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(AttachmentsView); diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js new file mode 100644 index 0000000000..15b0ffa4a9 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './AttachmentsConstants.js'; + +export default { + + toggleExpanded(dispatch, {path}) { + dispatch({ + type: actionTypes.TOGGLE_EXPANDED, + path + }); + }, + + onSelectNode(dispatch, {nodeName}) { + dispatch({ + type: actionTypes.SELECTED_NODE, + nodeName + }); + }, + + onUnselectNode(dispatch) { + dispatch({ + type: actionTypes.UNSELECTED_NODE + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js new file mode 100644 index 0000000000..33af476d9c --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js @@ -0,0 +1,55 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export const actionTypes = keyMirror({ + TOGGLE_EXPANDED: null, + SELECTED_NODE: null, + UNSELECTED_NODE: null +}); + +export const errorTypes = keyMirror({ + MISSING_FILE_IN_ZIP: i18n('missing file in zip'), + MISSING_FILE_IN_MANIFEST: i18n('missing file in manifest'), + MISSING_OR_ILLEGAL_FILE_TYPE_IN_MANIFEST: i18n('missing or illegal file type in manifest'), + FILE_IS_YML_WITHOUT_YML_EXTENSION: i18n('file is defined as a heat file but it doesn\'t have .yml or .yaml extension'), + FILE_IS_ENV_WITHOUT_ENV_EXTENSION: i18n('file is defined as an env file but it doesn\'t have .env extension'), + ILLEGAL_YAML_FILE_CONTENT: i18n('illegal yaml file content'), + ILLEGAL_HEAT_YAML_FILE_CONTENT: i18n('illegal HEAT yaml file content'), + MISSING_FILE_NAME_IN_MANIFEST: i18n('a file is written in manifest without file name'), + MISSING_ENV_FILE_IN_ZIP: i18n('missing env file in zip'), + ARTIFACT_NOT_IN_USE: i18n('artifact not in use') +}); + +export const nodeTypes = keyMirror({ + heat: i18n('Heat'), + volume: i18n('Volume'), + network: i18n('Network'), + artifact: i18n('Artifact'), + env: i18n('Environment'), + other: i18n('') +}); + +export const mouseActions = keyMirror({ + MOUSE_BUTTON_CLICK: 0 +}); + diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js new file mode 100644 index 0000000000..01f68aede8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js @@ -0,0 +1,199 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; +import {actionTypes} from './AttachmentsConstants.js'; + +const mapVolumeData = ({fileName, env, errors}) => ({ + name: fileName, + expanded: true, + type: 'volume', + children: env && [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }], + errors +}); + +const mapNetworkData = ({fileName, env, errors}) => ({ + name: fileName, + expanded: true, + type: 'network', + children: env && [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }], + errors +}); + +const mapArtifactsData = ({fileName, errors}) => ({ + name: fileName, + type: 'artifact', + errors +}); + +const mapOtherData = ({fileName, errors}) => ({ + name: fileName, + type: 'other', + errors +}); + + +const mapHeatData = ({fileName, env, nested, volume, network, artifacts, errors, other}) => ({ + name: fileName, + expanded: true, + type: 'heat', + errors, + children: [ + ...(volume ? volume.map(mapVolumeData) : []), + ...(network ? network.map(mapNetworkData) : []), + ...(env ? [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }] : []), + ...(artifacts ? artifacts.map(mapArtifactsData) : []), + ...(other ? other.map(mapOtherData) : []), + ...(nested ? nested.map(mapHeatData) : []) + ] +}); + +function createErrorList(node, parent, deep = 0, errorList = []) { + if (node.errors) { + errorList.push(...node.errors.map((error) => ({ + errorLevel: error.level, + errorMessage: error.message, + name: node.name, + hasParent: deep > 2, + parentName: parent.name, + type: node.type, + }))); + } + if (node.children && node.children.length) { + node.children.map((child) => createErrorList(child, node, deep + 1, errorList)); + } + return errorList; +} + +const mapValidationDataToTree = validationData => { + let {HEAT, volume, network, artifacts, other} = validationData.importStructure || {}; + return { + children: [ + { + name: 'HEAT', + expanded: true, + type: 'heat', + children: (HEAT ? HEAT.map(mapHeatData) : []) + }, + ...(artifacts ? [{ + name: 'artifacts', + expanded: true, + type: 'artifact', + children: (artifacts ? artifacts.map(mapArtifactsData) : []) + }] : []), + ...(network ? [{ + name: 'networks', + expanded: true, + type: 'network', + children: (network ? network.map(mapNetworkData) : []), + }] : []), + ...(volume ? [{ + name: 'volume', + expanded: true, + type: 'volume', + children: (volume ? volume.map(mapVolumeData) : []), + }] : []), + ...(other ? [{ + name: 'other', + expanded: true, + type: 'other', + children: (other ? other.map(mapOtherData) : []), + }] : []) + ] + }; +}; + +const toggleExpanded = (node, path) => { + let newNode = {...node}; + if (path.length === 0) { + newNode.expanded = !node.expanded; + } else { + let index = path[0]; + newNode.children = [ + ...node.children.slice(0, index), + toggleExpanded(node.children[index], path.slice(1)), + ...node.children.slice(index + 1) + ]; + } + return newNode; +}; + +const expandSelected = (node, selectedNode) => { + let shouldExpand = node.name === selectedNode; + let children = node.children && node.children.map(child => { + let {shouldExpand: shouldExpandChild, node: newChild} = expandSelected(child, selectedNode); + shouldExpand = shouldExpand || shouldExpandChild; + return newChild; + }); + + return { + node: { + ...node, + expanded: node.expanded || shouldExpand, + children + }, + shouldExpand + }; +}; + +export default (state = {attachmentsTree: {}}, action) => { + switch (action.type) { + case softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED: + let currentSoftwareProduct = action.response; + let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData) : {}; + let errorList = createErrorList(attachmentsTree); + return { + ...state, + attachmentsTree, + errorList + }; + case actionTypes.TOGGLE_EXPANDED: + return { + ...state, + attachmentsTree: toggleExpanded(state.attachmentsTree, action.path) + }; + case actionTypes.SELECTED_NODE: + let selectedNode = action.nodeName; + return { + ...state, + attachmentsTree: expandSelected(state.attachmentsTree, selectedNode).node, + selectedNode + }; + case actionTypes.UNSELECTED_NODE: + return { + ...state, + selectedNode: undefined + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx new file mode 100644 index 0000000000..7e2dda8d47 --- /dev/null +++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx @@ -0,0 +1,190 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import classNames from 'classnames'; +import Collapse from 'react-bootstrap/lib/Collapse.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import {nodeTypes, mouseActions} from './AttachmentsConstants'; + +const typeToIcon = Object.freeze({ + heat: 'building-o', + volume: 'database', + network: 'cloud', + artifact: 'gear', + env: 'server', + other: 'cube' +}); + +const leftPanelWidth = 250; + +class SoftwareProductAttachmentsView extends React.Component { + + static propTypes = { + attachmentsTree: React.PropTypes.object.isRequired + }; + state = { + treeWidth: '400', + }; + + render() { + let {attachmentsTree, errorList = []} = this.props; + + let {treeWidth} = this.state; + return ( + <div className='software-product-attachments'> + <div className='software-product-view'> + <div className='software-product-landing-view-right-side'> + <div className='software-product-attachments-main'> + <div className='software-product-attachments-tree' style={{'width' : treeWidth + 'px'}}> + <div className='tree-wrapper'> + { + attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind])) + } + </div> + </div> + <div className='software-product-attachments-separator' onMouseDown={e => this.onChangeTreeWidth(e)} /> + <div className='software-product-attachments-error-list'> + {errorList.length ? this.renderErrorList(errorList) : <div className='no-errors'>{attachmentsTree.children ? + i18n('VALIDATION SUCCESS') : i18n('THERE IS NO HEAT DATA TO PRESENT') }</div>} + </div> + </div> + </div> + </div> + </div> + ); + } + + + + renderNode(node, path) { + let isFolder = node.children && node.children.length > 0; + let {onSelectNode} = this.props; + return ( + <div key={node.name} className='tree-block-inside'> + { + <div onDoubleClick={() => this.props.toggleExpanded(path)} className={this.getTreeRowClassName(node.name)}> + { + isFolder && + <div onClick={() => this.props.toggleExpanded(path)} className={classNames('tree-node-expander', {'tree-node-expander-collapsed': !node.expanded})}> + <FontAwesome name='caret-down'/> + </div> + } + { + + <span className='tree-node-icon'> + <FontAwesome name={typeToIcon[node.type]}/> + </span> + } + { + + <span onClick={() => onSelectNode(node.name)} className={this.getTreeTextClassName(node)}> + {node.name} + </span> + } + </div> + } + { + isFolder && + <Collapse in={node.expanded}> + <div className='tree-node-children'> + { + node.children.map((child, ind) => this.renderNode(child, [...path, ind])) + } + </div> + </Collapse> + } + </div> + ); + } + + createErrorList(errorList, node, parent) { + if (node.errors) { + node.errors.forEach(error => errorList.push({ + error, + name: node.name, + parentName: parent.name, + type: node.type + })); + } + if (node.children && node.children.length) { + node.children.map((child) => this.createErrorList(errorList, child, node)); + } + } + + renderErrorList(errors) { + let prevError = {}; + let {selectedNode} = this.props; + return errors.map(error => { + let isSameNodeError = error.name === prevError.name && error.parentName === prevError.parentName; + prevError = error; + + return ( + <div + key={error.name + error.errorMessage + error.parentName} + + onClick={() => this.selectNode(error.name)} + className={classNames('error-item', {'clicked': selectedNode === error.name, 'shifted': !isSameNodeError})}> + <span className={classNames('error-item-file-type', {'strong': !isSameNodeError})}> + { + error.hasParent ? + i18n('{type} {name} in {parentName}: ', { + type: nodeTypes[error.type], + name: error.name, + parentName: error.parentName + }) : + i18n('{type} {name}: ', { + type: nodeTypes[error.type], + name: error.name + }) + } + </span> + <span className={`error-item-file-type ${error.errorLevel}`}> {error.errorMessage} </span> + </div> + ); + }); + } + + selectNode(currentSelectedNode) { + let {onUnselectNode, onSelectNode, selectedNode} = this.props; + if (currentSelectedNode !== selectedNode) { + onSelectNode(currentSelectedNode); + }else{ + onUnselectNode(); + } + + } + + getTreeRowClassName(name) { + let {hoveredNode, selectedNode} = this.props; + return classNames({ + 'tree-node-row': true, + 'tree-node-selected': name === hoveredNode, + 'tree-node-clicked': name === selectedNode + }); + } + + getTreeTextClassName(node) { + let {selectedNode} = this.props; + return classNames({ + 'tree-element-text': true, + 'error-status': node.errors, + 'error-status-selected': node.name === selectedNode + }); + } + + onChangeTreeWidth(e) { + if (e.button === mouseActions.MOUSE_BUTTON_CLICK) { + let onMouseMove = (e) => { + this.setState({treeWidth: e.clientX - leftPanelWidth}); + }; + let onMouseUp = () => { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + } +} + +export default SoftwareProductAttachmentsView; diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js new file mode 100644 index 0000000000..d39b2affd3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js @@ -0,0 +1,188 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import LicenseModelActionHelper from './licenseModel/LicenseModelActionHelper.js'; +import LicenseAgreementActionHelper from './licenseModel/licenseAgreement/LicenseAgreementActionHelper.js'; +import FeatureGroupsActionHelper from './licenseModel/featureGroups/FeatureGroupsActionHelper.js'; +import LicenseKeyGroupsActionHelper from './licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js'; +import EntitlementPoolsActionHelper from './licenseModel/entitlementPools/EntitlementPoolsActionHelper.js'; +import SoftwareProductActionHelper from './softwareProduct/SoftwareProductActionHelper.js'; +import SoftwareProductProcessesActionHelper from './softwareProduct/processes/SoftwareProductProcessesActionHelper.js'; +import SoftwareProductNetworksActionHelper from './softwareProduct/networks/SoftwareProductNetworksActionHelper.js'; +import SoftwareProductComponentsActionHelper from './softwareProduct/components/SoftwareProductComponentsActionHelper.js'; +import SoftwareProductComponentProcessesActionHelper from './softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js'; +import SoftwareProductComponentsNetworkActionHelper from './softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js'; +import SoftwareProductComponentsMonitoringAction from './softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js'; +import {actionTypes, enums} from './OnboardingConstants.js'; +import {navigationItems as SoftwareProductNavigationItems, actionTypes as SoftwareProductActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; +import store from 'sdc-app/AppStore.js'; + +function setCurrentScreen(dispatch, screen, props = {}) { + dispatch({ + type: actionTypes.SET_CURRENT_SCREEN, + currentScreen: { + screen, + props + } + }); +} + +function getCurrentLicenseModelVersion(licenseModelId) { + return store.getState().licenseModelList.find(({id}) => id === licenseModelId).version; +} + +export default { + + navigateToOnboardingCatalog(dispatch) { + LicenseModelActionHelper.fetchLicenseModels(dispatch); + SoftwareProductActionHelper.fetchSoftwareProductList(dispatch); + setCurrentScreen(dispatch, enums.SCREEN.ONBOARDING_CATALOG); + }, + + navigateToLicenseAgreements(dispatch, {licenseModelId, version}) { + if(!version) { + version = getCurrentLicenseModelVersion(licenseModelId); + } + LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId, version}); + LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => { + setCurrentScreen(dispatch, enums.SCREEN.LICENSE_AGREEMENTS, {licenseModelId}); + }); + }, + + navigateToFeatureGroups(dispatch, {licenseModelId, version}) { + if(!version) { + version = getCurrentLicenseModelVersion(licenseModelId); + } + FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId, version}); + setCurrentScreen(dispatch, enums.SCREEN.FEATURE_GROUPS, {licenseModelId}); + }, + + navigateToEntitlementPools(dispatch, {licenseModelId, version}) { + if(!version) { + version = getCurrentLicenseModelVersion(licenseModelId); + } + EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId, version}); + setCurrentScreen(dispatch, enums.SCREEN.ENTITLEMENT_POOLS, {licenseModelId}); + }, + + navigateToLicenseKeyGroups(dispatch, {licenseModelId, version}) { + if(!version) { + version = getCurrentLicenseModelVersion(licenseModelId); + } + LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version}); + setCurrentScreen(dispatch, enums.SCREEN.LICENSE_KEY_GROUPS, {licenseModelId}); + }, + + navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, version, licensingVersion}) { + SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId, version}).then(response => { + if(!licensingVersion) { + licensingVersion = response[0].licensingVersion; + } + if (!licenseModelId) { + licenseModelId = response[0].vendorId; + } + + SoftwareProductActionHelper.loadSoftwareProductDetailsData(dispatch, {licenseModelId, licensingVersion}); + SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(dispatch, {softwareProductId, version}); + + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, {softwareProductId, licenseModelId, version}); + }); + }, + + navigateToSoftwareProductDetails(dispatch, {softwareProductId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_DETAILS, {softwareProductId}); + }, + + navigateToSoftwareProductAttachments(dispatch, {softwareProductId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS, {softwareProductId}); + }, + + navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version}) { + if (softwareProductId) { + SoftwareProductProcessesActionHelper.fetchProcessesList(dispatch, {softwareProductId, version}); + } + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES, {softwareProductId}); + }, + + navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version}) { + if (softwareProductId) { + SoftwareProductNetworksActionHelper.fetchNetworksList(dispatch, {softwareProductId, version}); + } + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS, {softwareProductId}); + }, + + navigateToSoftwareProductComponents(dispatch, {softwareProductId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS, {softwareProductId}); + }, + + navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId, version}) { + if (componentId && softwareProductId) { + SoftwareProductComponentProcessesActionHelper.fetchProcessesList(dispatch, {componentId, softwareProductId, version}); + } + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES, {softwareProductId, componentId}); + }, + + navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId}){ + if (componentId && softwareProductId) { + SoftwareProductComponentsMonitoringAction.fetchExistingFiles(dispatch, {componentId, softwareProductId}); + } + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING, {softwareProductId, componentId}); + }, + + navigateToComponentStorage(dispatch, {softwareProductId, componentId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE, {softwareProductId, componentId}); + }, + + navigateToComponentCompute(dispatch, {softwareProductId, componentId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE, {softwareProductId, componentId}); + }, + + navigateToComponentNetwork(dispatch, {softwareProductId, componentId, version}) { + SoftwareProductComponentsNetworkActionHelper.fetchNICsList(dispatch, {softwareProductId, componentId, version}); + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK, {softwareProductId, componentId}); + }, + + navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version}) { + if (componentId && softwareProductId) { + SoftwareProductComponentsActionHelper.fetchSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId: componentId, version}); + SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(dispatch, { + softwareProductId, + vspComponentId: componentId, + version + }); + } + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL, {softwareProductId, componentId}); + }, + + navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version}) { + this.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version}); + dispatch({ + type: SoftwareProductActionTypes.TOGGLE_NAVIGATION_ITEM, + mapOfExpandedIds: { + [SoftwareProductNavigationItems.COMPONENTS]: true, + [SoftwareProductNavigationItems.COMPONENTS + '|' + componentId]: true + } + }); + }, + + navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId}) { + setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING, {softwareProductId, componentId}); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js new file mode 100644 index 0000000000..4772c8d9af --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import {default as OnboardingCatalogView, catalogItemTypes} from './OnboardingCatalogView.jsx'; +import OnboardingActionHelper from './OnboardingActionHelper.js'; +import LicenseModelCreationActionHelper from './licenseModel/creation/LicenseModelCreationActionHelper.js'; +import SoftwareProductCreationActionHelper from './softwareProduct/creation/SoftwareProductCreationActionHelper.js'; + + +const mapStateToProps = ({licenseModelList, softwareProductList, softwareProduct: {softwareProductCreation}, licenseModel: {licenseModelCreation}}) => { + + let modalToShow; + + if(licenseModelCreation.data) { + modalToShow = catalogItemTypes.LICENSE_MODEL; + } else if(softwareProductCreation.showModal && softwareProductCreation.data) { + modalToShow = catalogItemTypes.SOFTWARE_PRODUCT; + } + + return { + licenseModelList, + softwareProductList, + modalToShow + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onSelectLicenseModel({id: licenseModelId}) { + OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId}); + }, + onSelectSoftwareProduct(softwareProduct) { + let {id: softwareProductId, vendorId: licenseModelId, licensingVersion} = softwareProduct; + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, licensingVersion}); + }, + onAddSoftwareProductClick: () => SoftwareProductCreationActionHelper.open(dispatch), + onAddLicenseModelClick: () => LicenseModelCreationActionHelper.open(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(OnboardingCatalogView); diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx new file mode 100644 index 0000000000..f2a9db1342 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx @@ -0,0 +1,147 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import objectValues from 'lodash/values.js'; +import LicenseModelCreation from './licenseModel/creation/LicenseModelCreation.js'; +import SoftwareProductCreation from './softwareProduct/creation/SoftwareProductCreation.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import classnames from 'classnames'; +import ExpandableInput from 'nfvo-components/input/ExpandableInput.jsx'; + +export const catalogItemTypes = Object.freeze({ + LICENSE_MODEL: 'license-model', + SOFTWARE_PRODUCT: 'software-product' +}); + +const catalogItemTypeClasses = { + LICENSE_MODEL: 'license-model-type', + SOFTWARE_PRODUCT: 'software-product-type' +}; + +class OnboardingCatalogView extends React.Component { + + constructor(props) { + super(props); + this.state = {searchValue: ''}; + this.handleSearch = this.handleSearch.bind(this); + } + + handleSearch(event){ + this.setState({searchValue: event.target.value}); + } + + static propTypes = { + licenseModelList: React.PropTypes.array, + softwareProductList: React.PropTypes.array, + modalToShow: React.PropTypes.oneOf(objectValues(catalogItemTypes)), + onSelectLicenseModel: React.PropTypes.func.isRequired, + onSelectSoftwareProduct: React.PropTypes.func.isRequired, + onAddLicenseModelClick: React.PropTypes.func.isRequired, + onAddSoftwareProductClick: React.PropTypes.func.isRequired + }; + + getModalDetails() { + const {modalToShow} = this.props; + switch (modalToShow) { + case catalogItemTypes.LICENSE_MODEL: + return { + title: i18n('New License Model'), + element: <LicenseModelCreation/> + }; + case catalogItemTypes.SOFTWARE_PRODUCT: + return { + title: i18n('New Software Product'), + element: <SoftwareProductCreation/> + }; + } + } + + render() { + const modalDetails = this.getModalDetails(); + const {licenseModelList, softwareProductList, onSelectLicenseModel, onSelectSoftwareProduct, onAddLicenseModelClick, onAddSoftwareProductClick, modalToShow} = this.props; + + return ( + <div className='catalog-view'> + <div className='catalog-header'> + <div className='catalog-header-title'>{i18n('Onboarding Catalog')}</div> + <ExpandableInput + onChange={this.handleSearch} + iconType='search'/> + </div> + <div className='catalog-list'> + + <div className='create-catalog-item tile'> + <div className='plus-section'> + <div className='plus-icon-button'/> + <span>{i18n('ADD')}</span> + </div> + <div className='primary-btn new-license-model'> + <span + className='primary-btn-text' + onClick={() => onAddLicenseModelClick()}>{i18n('New License Model')}</span></div> + <div className='primary-btn'> + <span + className='primary-btn-text' + onClick={() => onAddSoftwareProductClick()}>{i18n('New Vendor Software Product')}</span> + </div> + </div> + {licenseModelList.filter(vlm => vlm.vendorName.toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1).map(licenseModel => this.renderTile( + { + ...licenseModel, + name: licenseModel.vendorName + }, + catalogItemTypeClasses.LICENSE_MODEL, + () => onSelectLicenseModel(licenseModel)) + )} + {softwareProductList.filter(vsp => vsp.name.toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1).map(softwareProduct => this.renderTile(softwareProduct, + catalogItemTypeClasses.SOFTWARE_PRODUCT, + () => onSelectSoftwareProduct(softwareProduct)) + )} + </div> + <Modal + show={Boolean(modalDetails)} + className={`${this.getCatalogItemTypeClassByItemType(modalToShow)}-modal`}> + <Modal.Header> + <Modal.Title>{modalDetails && modalDetails.title}</Modal.Title> + </Modal.Header> + <Modal.Body> + { + modalDetails && modalDetails.element + } + </Modal.Body> + </Modal> + </div> + ); + + } + + getCatalogItemTypeClassByItemType(catalogItemType) { + switch (catalogItemType) { + case catalogItemTypes.LICENSE_MODEL: + return catalogItemTypeClasses.LICENSE_MODEL; + case catalogItemTypes.SOFTWARE_PRODUCT: + return catalogItemTypeClasses.SOFTWARE_PRODUCT; + } + } + + renderTile(catalogItemData, catalogItemTypeClass, onSelect) { + let {status: itemStatus} = VersionControllerUtils.getCheckOutStatusKindByUserID(catalogItemData.status, catalogItemData.lockingUser); + return ( + <div className='catalog-tile tile' key={catalogItemData.id} onClick={() => onSelect()}> + <div className={`catalog-tile-type ${catalogItemTypeClass}`}/> + <div className='catalog-tile-icon'> + <div className={`icon ${catalogItemTypeClass}-icon`}></div> + </div> + <div className='catalog-tile-content'> + <div className='catalog-tile-item-details'> + <div className='catalog-tile-item-name'>{catalogItemData.name}</div> + <div className='catalog-tile-item-version'>V {catalogItemData.version}</div> + </div> + <div className={classnames('catalog-tile-check-in-status', {'sprite-new checkout-editable-status-icon': itemStatus === 'Locked'})}> + </div> + </div> + </div> + ); + } +} +export default OnboardingCatalogView; diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js new file mode 100644 index 0000000000..d9c177f606 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js @@ -0,0 +1,72 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + SET_CURRENT_SCREEN: null, + SET_CURRENT_LICENSE_MODEL: null +}); + +export const enums = keyMirror({ + + BREADCRUMS: { + LICENSE_MODEL: 'LICENSE_MODEL', + LICENSE_AGREEMENTS: 'LICENSE_AGREEMENTS', + FEATURE_GROUPS: 'FEATURE_GROUPS', + ENTITLEMENT_POOLS: 'ENTITLEMENT_POOLS', + LICENSE_KEY_GROUPS: 'LICENSE_KEY_GROUPS', + + SOFTWARE_PRODUCT: 'SOFTWARE_PRODUCT', + SOFTWARE_PRODUCT_DETAILS: 'SOFTWARE_PRODUCT_DETAILS', + SOFTWARE_PRODUCT_ATTACHMENTS: 'SOFTWARE_PRODUCT_ATTACHMENTS', + SOFTWARE_PRODUCT_PROCESSES: 'SOFTWARE_PRODUCT_PROCESSES', + SOFTWARE_PRODUCT_NETWORKS: 'SOFTWARE_PRODUCT_NETWORKS', + SOFTWARE_PRODUCT_COMPONENTS: 'SOFTWARE_PRODUCT_COMPONENTS', + SOFTWARE_PRODUCT_COMPONENT_PROCESSES: 'SOFTWARE_PRODUCT_COMPONENT_PROCESSES', + SOFTWARE_PRODUCT_COMPONENT_STORAGE: 'SOFTWARE_PRODUCT_COMPONENT_STORAGE', + SOFTWARE_PRODUCT_COMPONENT_GENERAL: 'SOFTWARE_PRODUCT_COMPONENT_GENERAL', + SOFTWARE_PRODUCT_COMPONENT_COMPUTE: 'SOFTWARE_PRODUCT_COMPONENT_COMPUTE', + SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: 'SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING', + SOFTWARE_PRODUCT_COMPONENT_MONITORING: 'SOFTWARE_PRODUCT_COMPONENT_MONITORING' + }, + + SCREEN: { + ONBOARDING_CATALOG: null, + LICENSE_AGREEMENTS: null, + FEATURE_GROUPS: null, + ENTITLEMENT_POOLS: null, + LICENSE_KEY_GROUPS: null, + + SOFTWARE_PRODUCT_LANDING_PAGE: null, + SOFTWARE_PRODUCT_DETAILS: null, + SOFTWARE_PRODUCT_ATTACHMENTS: null, + SOFTWARE_PRODUCT_PROCESSES: null, + SOFTWARE_PRODUCT_NETWORKS: null, + SOFTWARE_PRODUCT_COMPONENTS: null, + SOFTWARE_PRODUCT_COMPONENT_PROCESSES: null, + SOFTWARE_PRODUCT_COMPONENT_COMPUTE: null, + SOFTWARE_PRODUCT_COMPONENT_STORAGE: null, + SOFTWARE_PRODUCT_COMPONENT_NETWORK: null, + SOFTWARE_PRODUCT_COMPONENT_GENERAL: null, + SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: null, + SOFTWARE_PRODUCT_COMPONENT_MONITORING: null + } +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx b/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx new file mode 100644 index 0000000000..c4627b11b3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx @@ -0,0 +1,501 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import {connect} from 'react-redux'; +import isEqual from 'lodash/isEqual.js'; +import objectValues from 'lodash/values.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Application from 'sdc-app/Application.jsx'; +import store from 'sdc-app/AppStore.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +import OnboardingCatalog from './OnboardingCatalog.js'; +import LicenseModel from './licenseModel/LicenseModel.js'; +import LicenseAgreementListEditor from './licenseModel/licenseAgreement/LicenseAgreementListEditor.js'; +import FeatureGroupListEditor from './licenseModel/featureGroups/FeatureGroupListEditor.js'; +import LicenseKeyGroupsListEditor from './licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js'; +import EntitlementPoolsListEditor from './licenseModel/entitlementPools/EntitlementPoolsListEditor.js'; +import SoftwareProduct from './softwareProduct/SoftwareProduct.js'; +import SoftwareProductLandingPage from './softwareProduct/landingPage/SoftwareProductLandingPage.js'; +import SoftwareProductDetails from './softwareProduct/details/SoftwareProductDetails.js'; +import SoftwareProductAttachments from './softwareProduct/attachments/SoftwareProductAttachments.js'; +import SoftwareProductProcesses from './softwareProduct/processes/SoftwareProductProcesses.js'; +import SoftwareProductNetworks from './softwareProduct/networks/SoftwareProductNetworks.js'; +import SoftwareProductComponentsList from './softwareProduct/components/SoftwareProductComponentsList.js'; +import SoftwareProductComponentProcessesList from './softwareProduct/components/processes/SoftwareProductComponentProcessesList.js'; +import SoftwareProductComponentStorage from './softwareProduct/components/storage/SoftwareProductComponentStorage.js'; +import SoftwareProductComponentsNetworkList from './softwareProduct/components/network/SoftwareProductComponentsNetworkList.js'; +import SoftwareProductComponentsGeneral from './softwareProduct/components/general/SoftwareProductComponentsGeneral.js'; +import SoftwareProductComponentsCompute from './softwareProduct/components/compute/SoftwareProductComponentCompute.js'; +import SoftwareProductComponentLoadBalancing from './softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js'; +import SoftwareProductComponentsMonitoring from './softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js'; +import {navigationItems as SoftwareProductNavigationItems, actionTypes as SoftwareProductActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; + +import {enums} from './OnboardingConstants.js'; +import OnboardingActionHelper from './OnboardingActionHelper.js'; + + +class OnboardingView extends React.Component { + static propTypes = { + currentScreen: React.PropTypes.shape({ + screen: React.PropTypes.oneOf(objectValues(enums.SCREEN)).isRequired, + props: React.PropTypes.object.isRequired + }).isRequired + }; + + componentDidMount() { + let element = ReactDOM.findDOMNode(this); + element.addEventListener('click', event => { + if (event.target.tagName === 'A') { + event.preventDefault(); + } + }); + ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType => + element.addEventListener(eventType, event => event.stopPropagation()) + ); + } + + render() { + let {currentScreen} = this.props; + let {screen, props} = currentScreen; + + return ( + <div className='dox-ui dox-ui-punch-out dox-ui-punch-out-full-page'> + {(() => { + switch (screen) { + case enums.SCREEN.ONBOARDING_CATALOG: + return <OnboardingCatalog {...props}/>; + + case enums.SCREEN.LICENSE_AGREEMENTS: + case enums.SCREEN.FEATURE_GROUPS: + case enums.SCREEN.ENTITLEMENT_POOLS: + case enums.SCREEN.LICENSE_KEY_GROUPS: + return ( + <LicenseModel currentScreen={currentScreen}> + { + (()=>{ + switch(screen) { + case enums.SCREEN.LICENSE_AGREEMENTS: + return <LicenseAgreementListEditor {...props}/>; + case enums.SCREEN.FEATURE_GROUPS: + return <FeatureGroupListEditor {...props}/>; + case enums.SCREEN.ENTITLEMENT_POOLS: + return <EntitlementPoolsListEditor {...props}/>; + case enums.SCREEN.LICENSE_KEY_GROUPS: + return <LicenseKeyGroupsListEditor {...props}/>; + } + })() + } + </LicenseModel> + ); + + case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS: + case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS: + case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING: + return ( + <SoftwareProduct currentScreen={currentScreen}> + { + (()=>{ + switch(screen) { + case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE: + return <SoftwareProductLandingPage {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS: + return <SoftwareProductDetails {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS: + return <SoftwareProductAttachments className='no-padding-content-area' {...props} />; + case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES: + return <SoftwareProductProcesses {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS: + return <SoftwareProductNetworks {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS: + return <SoftwareProductComponentsList {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES: + return <SoftwareProductComponentProcessesList {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE: + return <SoftwareProductComponentStorage {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK: + return <SoftwareProductComponentsNetworkList {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL: + return <SoftwareProductComponentsGeneral{...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE: + return <SoftwareProductComponentsCompute {...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: + return <SoftwareProductComponentLoadBalancing{...props}/>; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING: + return <SoftwareProductComponentsMonitoring {...props}/>; + } + })() + } + </SoftwareProduct> + ); + } + })()} + </div> + ); + } +} +const mapStateToProps = ({currentScreen}) => ({currentScreen}); +let Onboarding = connect(mapStateToProps, null)(OnboardingView); + +export default class OnboardingPunchOut { + + render({options: {data, apiRoot, apiHeaders}, onEvent}, element) { + if (!this.unsubscribeFromStore) { + this.unsubscribeFromStore = store.subscribe(() => this.handleStoreChange()); + } + + if (!this.isConfigSet) { + Configuration.setATTApiRoot(apiRoot); + Configuration.setATTApiHeaders(apiHeaders); + this.isConfigSet = true; + } + + this.onEvent = (...args) => onEvent(...args); + this.handleData(data); + + if (!this.rendered) { + ReactDOM.render( + <Application> + <Onboarding/> + </Application>, + element + ); + this.rendered = true; + } + } + + unmount(element) { + ReactDOM.unmountComponentAtNode(element); + this.rendered = false; + this.unsubscribeFromStore(); + this.unsubscribeFromStore = null; + } + + handleData(data) { + let {breadcrumbs: {selectedKeys = []} = {}} = data; + let dispatch = action => store.dispatch(action); + let {softwareProductList, softwareProduct: {softwareProductEditor: {data: {id: currentSoftwareProductId, version: currentSoftwareProductVersion} = {}}}, + licenseModelList, licenseModel: {licenseModelEditor: {data: {id: currentLicenseModelId, version: currentLicenseModelVersion} = {}}}} = store.getState(); + + if (this.programmaticBreadcrumbsUpdate) { + this.prevSelectedKeys = selectedKeys; + this.programmaticBreadcrumbsUpdate = false; + return; + } + + if (!isEqual(selectedKeys, this.prevSelectedKeys)) { + this.breadcrumbsPrefixSelected = isEqual(selectedKeys, this.prevSelectedKeys && this.prevSelectedKeys.slice(0, selectedKeys.length)); + this.prevSelectedKeys = selectedKeys; + + if (selectedKeys.length === 0) { + OnboardingActionHelper.navigateToOnboardingCatalog(dispatch); + } else if (selectedKeys.length === 1 || selectedKeys[1] === enums.BREADCRUMS.LICENSE_MODEL) { + let [licenseModelId, , licenseModelScreen] = selectedKeys; + if (!licenseModelScreen) { + licenseModelScreen = enums.BREADCRUMS.LICENSE_AGREEMENTS; + } + if(currentLicenseModelId !== licenseModelId) { + currentLicenseModelVersion = licenseModelList.find(lm => lm.id === licenseModelId).version; + } + switch (licenseModelScreen) { + case enums.BREADCRUMS.LICENSE_AGREEMENTS: + OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId, version: currentLicenseModelVersion}); + break; + case enums.BREADCRUMS.FEATURE_GROUPS: + OnboardingActionHelper.navigateToFeatureGroups(dispatch, {licenseModelId, version: currentLicenseModelVersion}); + break; + case enums.BREADCRUMS.ENTITLEMENT_POOLS: + OnboardingActionHelper.navigateToEntitlementPools(dispatch, {licenseModelId, version: currentLicenseModelVersion}); + break; + case enums.BREADCRUMS.LICENSE_KEY_GROUPS: + OnboardingActionHelper.navigateToLicenseKeyGroups(dispatch, {licenseModelId, version: currentLicenseModelVersion}); + break; + } + } else if (selectedKeys.length <= 4 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT) { + let [licenseModelId, , softwareProductId, softwareProductScreen] = selectedKeys; + let softwareProduct = softwareProductId ? + softwareProductList.find(({id}) => id === softwareProductId) : + softwareProductList.find(({vendorId}) => vendorId === licenseModelId); + if (!softwareProductId) { + softwareProductId = softwareProduct.id; + } + if(currentSoftwareProductId !== softwareProductId) { + currentSoftwareProductVersion = softwareProduct.version; + } + switch (softwareProductScreen) { + case enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS: + OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, {softwareProductId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS: + OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES: + OnboardingActionHelper.navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version: currentSoftwareProductVersion}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS: + OnboardingActionHelper.navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version: currentSoftwareProductVersion}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS: + OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId, version: currentSoftwareProductVersion}); + dispatch({ + type: SoftwareProductActionTypes.TOGGLE_NAVIGATION_ITEM, + mapOfExpandedIds: { + [SoftwareProductNavigationItems.COMPONENTS]: true + } + }); + break; + default: + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version: currentSoftwareProductVersion}); + break; + } + }else if (selectedKeys.length === 5 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT && selectedKeys[3] === enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS) { + let [licenseModelId, , softwareProductId, , componentId] = selectedKeys; + let softwareProduct = softwareProductId ? + softwareProductList.find(({id}) => id === softwareProductId) : + softwareProductList.find(({vendorId}) => vendorId === licenseModelId); + if (!softwareProductId) { + softwareProductId = softwareProduct.id; + } + if(currentSoftwareProductId !== softwareProductId) { + currentSoftwareProductVersion = softwareProduct.version; + } + OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version: currentSoftwareProductVersion}); + }else if (selectedKeys.length === 6 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT && selectedKeys[3] === enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS) { + let [licenseModelId, , softwareProductId, , componentId, componentScreen] = selectedKeys; + let softwareProduct = softwareProductId ? + softwareProductList.find(({id}) => id === softwareProductId) : + softwareProductList.find(({vendorId}) => vendorId === licenseModelId); + if (!softwareProductId) { + softwareProductId = softwareProduct.id; + } + if(currentSoftwareProductId !== softwareProductId) { + currentSoftwareProductVersion = softwareProduct.version; + } + switch (componentScreen) { + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL: + OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version: currentSoftwareProductVersion}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE: + OnboardingActionHelper.navigateToComponentCompute(dispatch, {softwareProductId, componentId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: + OnboardingActionHelper.navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK: + OnboardingActionHelper.navigateToComponentNetwork(dispatch, {softwareProductId, componentId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE: + OnboardingActionHelper.navigateToComponentStorage(dispatch, {softwareProductId, componentId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES: + OnboardingActionHelper.navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId}); + break; + case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING: + OnboardingActionHelper.navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId}); + break; + } + } else { + console.error('Unknown breadcrumbs path: ', selectedKeys); + } + } + } + + handleStoreChange() { + let {currentScreen, licenseModelList, softwareProductList} = store.getState(); + let breadcrumbsData = {currentScreen, licenseModelList, softwareProductList}; + + if (!isEqual(breadcrumbsData, this.prevBreadcrumbsData) || this.breadcrumbsPrefixSelected) { + this.prevBreadcrumbsData = breadcrumbsData; + this.breadcrumbsPrefixSelected = false; + this.programmaticBreadcrumbsUpdate = true; + + let breadcrumbs = this.buildBreadcrumbs(breadcrumbsData); + this.onEvent('breadcrumbsupdated', breadcrumbs); + } + } + + buildBreadcrumbs({currentScreen: {screen, props}, licenseModelList, softwareProductList}) { + let componentsList; + if(props.componentId) { + componentsList = store.getState().softwareProduct.softwareProductComponents.componentsList; + } + let screenToBreadcrumb; + switch (screen) { + case enums.SCREEN.ONBOARDING_CATALOG: + return []; + + case enums.SCREEN.LICENSE_AGREEMENTS: + case enums.SCREEN.FEATURE_GROUPS: + case enums.SCREEN.ENTITLEMENT_POOLS: + case enums.SCREEN.LICENSE_KEY_GROUPS: + screenToBreadcrumb = { + [enums.SCREEN.LICENSE_AGREEMENTS]: enums.BREADCRUMS.LICENSE_AGREEMENTS, + [enums.SCREEN.FEATURE_GROUPS]: enums.BREADCRUMS.FEATURE_GROUPS, + [enums.SCREEN.ENTITLEMENT_POOLS]: enums.BREADCRUMS.ENTITLEMENT_POOLS, + [enums.SCREEN.LICENSE_KEY_GROUPS]: enums.BREADCRUMS.LICENSE_KEY_GROUPS + }; + return [ + { + selectedKey: props.licenseModelId, + menuItems: licenseModelList.map(({id, vendorName}) => ({ + key: id, + displayText: vendorName + })) + }, + { + selectedKey: enums.BREADCRUMS.LICENSE_MODEL, + menuItems: [{ + key: enums.BREADCRUMS.LICENSE_MODEL, + displayText: i18n('License Model') + }, + ...(softwareProductList.findIndex(({vendorId}) => vendorId === props.licenseModelId) === -1 ? [] : [{ + key: enums.BREADCRUMS.SOFTWARE_PRODUCT, + displayText: i18n('Software Products') + }])] + }, { + selectedKey: screenToBreadcrumb[screen], + menuItems: [{ + key: enums.BREADCRUMS.LICENSE_AGREEMENTS, + displayText: i18n('License Agreements') + }, { + key: enums.BREADCRUMS.FEATURE_GROUPS, + displayText: i18n('Feature Groups') + }, { + key: enums.BREADCRUMS.ENTITLEMENT_POOLS, + displayText: i18n('Entitlement Pools') + }, { + key: enums.BREADCRUMS.LICENSE_KEY_GROUPS, + displayText: i18n('License Key Groups') + }] + } + ]; + + case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS: + case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS: + case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS: + + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING: + screenToBreadcrumb = { + [enums.SCREEN.SOFTWARE_PRODUCT_DETAILS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS, + [enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS, + [enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES]: enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES, + [enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS + }; + let componentScreenToBreadcrumb = { + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING + }; + let licenseModelId = softwareProductList.find(({id}) => id === props.softwareProductId).vendorId; + let returnedBreadcrumb = [ + { + selectedKey: licenseModelId, + menuItems: licenseModelList.map(({id, vendorName}) => ({ + key: id, + displayText: vendorName + })) + }, + { + selectedKey: enums.BREADCRUMS.SOFTWARE_PRODUCT, + menuItems: [{ + key: enums.BREADCRUMS.LICENSE_MODEL, + displayText: i18n('License Model') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT, + displayText: i18n('Software Products') + }] + }, + { + selectedKey: props.softwareProductId, + menuItems: softwareProductList + .filter(({vendorId}) => vendorId === licenseModelId) + .map(({id, name}) => ({ + key: id, + displayText: name + })) + }, + ...(screen === enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE ? [] : [{ + selectedKey: screenToBreadcrumb[screen] || enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS, + menuItems: [{ + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS, + displayText: i18n('General') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS, + displayText: i18n('Attachments') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES, + displayText: i18n('Process Details') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS, + displayText: i18n('Networks') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS, + displayText: i18n('Components') + }] + }]) + ]; + if(props.componentId) { + returnedBreadcrumb = [ + ...returnedBreadcrumb, { + selectedKey: props.componentId, + menuItems: componentsList + .map(({id, displayName}) => ({ + key: id, + displayText: displayName + })) + }, + ...[{ + selectedKey: componentScreenToBreadcrumb[screen], + menuItems: [{ + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL, + displayText: i18n('General') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE, + displayText: i18n('Compute') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING, + displayText: i18n('High Availability & Load Balancing') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK, + displayText: i18n('Networks') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE, + displayText: i18n('Storage') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES, + displayText: i18n('Process Details') + }, { + key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING, + displayText: i18n('Monitoring') + }] + }] + ]; + } + return returnedBreadcrumb; + } + } +} diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js new file mode 100644 index 0000000000..9af2427243 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js @@ -0,0 +1,29 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes, enums} from './OnboardingConstants.js'; + +export const currentScreenReducer = (state = {screen: enums.SCREEN.ONBOARDING_CATALOG, props: {}}, action) => { + if (action.type === actionTypes.SET_CURRENT_SCREEN) { + return action.currentScreen; + } + return state; +}; + diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js new file mode 100644 index 0000000000..92d53a3d4f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {currentScreenReducer} from './OnboardingReducers.js'; +import licenseModelListReducer from './licenseModel/LicenseModelListReducer.js'; +import finalizedLicenseModelListReducer from './licenseModel/FinalizedLicenseModelListReducer.js'; +import licenseModelReducer from './licenseModel/LicenseModelReducer.js'; +import softwareProductReducer from './softwareProduct/SoftwareProductReducer.js'; +import softwareProductListReducer from './softwareProduct/SoftwareProductListReducer.js'; + + +export default { + currentScreen: currentScreenReducer, + licenseModelList: licenseModelListReducer, + finalizedLicenseModelList: finalizedLicenseModelListReducer, + licenseModel: licenseModelReducer, + softwareProduct: softwareProductReducer, + softwareProductList: softwareProductListReducer +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js new file mode 100644 index 0000000000..a851e77dc8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LicenseModelConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.FINALIZED_LICENSE_MODELS_LIST_LOADED: + return [...action.response.results]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js new file mode 100644 index 0000000000..ad91a0da65 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js @@ -0,0 +1,147 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import TabulatedEditor from 'src/nfvo-components/editor/TabulatedEditor.jsx'; + +import {enums} from 'sdc-app/onboarding/OnboardingConstants.js'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; + +import {navigationItems} from './LicenseModelConstants.js'; +import LicenseModelActionHelper from './LicenseModelActionHelper.js'; +import LicenseAgreementActionHelper from './licenseAgreement/LicenseAgreementActionHelper.js'; +import FeatureGroupsActionHelper from './featureGroups/FeatureGroupsActionHelper.js'; +import EntitlementPoolsActionHelper from './entitlementPools/EntitlementPoolsActionHelper.js'; +import LicenseKeyGroupsActionHelper from './licenseKeyGroups/LicenseKeyGroupsActionHelper.js'; + + +const buildNavigationBarProps = (licenseModel, screen) => { + const {id, vendorName, version} = licenseModel; + const meta = {version}; + + const groups = [{ + id, + name: vendorName, + items: [ + { + id: navigationItems.LICENSE_AGREEMENTS, + name: i18n('License Agreements'), + meta + }, + { + id: navigationItems.FEATURE_GROUPS, + name: i18n('Feature Groups'), + meta + }, + { + id: navigationItems.ENTITLEMENT_POOLS, + name: i18n('Entitlement Pools'), + meta + }, + { + id: navigationItems.LICENSE_KEY_GROUPS, + name: i18n('License Key Groups'), + meta + } + ] + }]; + + const activeItemId = ({ + [enums.SCREEN.LICENSE_AGREEMENTS]: navigationItems.LICENSE_AGREEMENTS, + [enums.SCREEN.FEATURE_GROUPS]: navigationItems.FEATURE_GROUPS, + [enums.SCREEN.ENTITLEMENT_POOLS]: navigationItems.ENTITLEMENT_POOLS, + [enums.SCREEN.LICENSE_KEY_GROUPS]: navigationItems.LICENSE_KEY_GROUPS + })[screen]; + + return { + activeItemId, groups + }; +}; + + +const buildVersionControllerProps = (licenseModel) => { + let {version, viewableVersions, status: currentStatus, lockingUser} = licenseModel; + let {status, isCheckedOut} = (currentStatus === versionStatusEnum.CHECK_OUT_STATUS) ? + VersionControllerUtils.getCheckOutStatusKindByUserID(currentStatus, lockingUser) : + {status: currentStatus, isCheckedOut: false}; + + return { + version, + viewableVersions, + status, + isCheckedOut + }; +}; + + +const mapStateToProps = ({licenseModel: {licenseModelEditor}}, {currentScreen: {screen}}) => { + return { + versionControllerProps: buildVersionControllerProps(licenseModelEditor.data), + navigationBarProps: buildNavigationBarProps(licenseModelEditor.data, screen) + }; +}; + + +const mapActionsToProps = (dispatch, {currentScreen: {screen, props: {licenseModelId}}}) => { + return { + onVersionControllerAction: action => + LicenseModelActionHelper.performVCAction(dispatch, {licenseModelId, action}).then(() => { + switch(screen) { + case enums.SCREEN.LICENSE_AGREEMENTS: + LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId}); + break; + case enums.SCREEN.FEATURE_GROUPS: + FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId}); + break; + case enums.SCREEN.ENTITLEMENT_POOLS: + EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId}); + break; + case enums.SCREEN.LICENSE_KEY_GROUPS: + LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId}); + break; + } + }), + onVersionSwitching: version => LicenseAgreementActionHelper.switchVersion(dispatch, {licenseModelId, version}), + onClose: () => OnboardingActionHelper.navigateToOnboardingCatalog(dispatch), + + onNavigate: ({id, meta: {version}}) => { + switch(id) { + case navigationItems.LICENSE_AGREEMENTS: + OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId, version}); + break; + case navigationItems.FEATURE_GROUPS: + OnboardingActionHelper.navigateToFeatureGroups(dispatch, {licenseModelId, version}); + break; + case navigationItems.ENTITLEMENT_POOLS: + OnboardingActionHelper.navigateToEntitlementPools(dispatch, {licenseModelId, version}); + break; + case navigationItems.LICENSE_KEY_GROUPS: + OnboardingActionHelper.navigateToLicenseKeyGroups(dispatch, {licenseModelId, version}); + break; + } + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(TabulatedEditor); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js new file mode 100644 index 0000000000..a379a2c40f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js @@ -0,0 +1,101 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes} from './LicenseModelConstants.js'; +import {actionsEnum as vcActionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; + +function baseUrl() { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/`; +} + +function fetchLicenseModels() { + return RestAPIUtil.fetch(baseUrl()); +} + +function fetchFinalizedLicenseModels() { + return RestAPIUtil.fetch(`${baseUrl()}?versionFilter=Final`); +} + +function fetchLicenseModelById(licenseModelId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl()}${licenseModelId}${versionQuery}`); +} + +function putLicenseModelAction(id, action) { + return RestAPIUtil.save(`${baseUrl()}${id}/actions`, {action: action}); +} + +const LicenseModelActionHelper = { + + fetchLicenseModels(dispatch) { + return fetchLicenseModels().then(response => { + dispatch({ + type: actionTypes.LICENSE_MODELS_LIST_LOADED, + response + }); + }); + }, + + fetchFinalizedLicenseModels(dispatch) { + return fetchFinalizedLicenseModels().then(response => dispatch({ + type: actionTypes.FINALIZED_LICENSE_MODELS_LIST_LOADED, + response + })); + + }, + + fetchLicenseModelById(dispatch, {licenseModelId, version}) { + return fetchLicenseModelById(licenseModelId, version).then(response => { + if(version) { + response.version = version; + } + dispatch({ + type: actionTypes.LICENSE_MODEL_LOADED, + response + }); + }); + }, + + addLicenseModel(dispatch, {licenseModel}){ + dispatch({ + type: actionTypes.ADD_LICENSE_MODEL, + licenseModel + }); + }, + + performVCAction(dispatch, {licenseModelId, action}) { + return putLicenseModelAction(licenseModelId, action).then(() => { + if(action === vcActionsEnum.SUBMIT){ + dispatch({ + type: NotificationConstants.NOTIFY_SUCCESS, + data: {title: i18n('Submit Succeeded'), msg: i18n('This license model successfully submitted'), timeout: 2000} + }); + } + return LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId}); + }); + } +}; + +export default LicenseModelActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js new file mode 100644 index 0000000000..13fa9f5284 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + LICENSE_MODEL_LOADED: null, + LICENSE_MODELS_LIST_LOADED: null, + FINALIZED_LICENSE_MODELS_LIST_LOADED: null, + ADD_LICENSE_MODEL: null, + EDIT_LICENSE_MODEL: null +}); + +export const navigationItems = keyMirror({ + LICENSE_AGREEMENTS: 'License Agreements', + FEATURE_GROUPS: 'Feature Groups', + ENTITLEMENT_POOLS: 'Entitlement Pools', + LICENSE_KEY_GROUPS: 'License Key Groups' +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js new file mode 100644 index 0000000000..e92e32aa9e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LicenseModelConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.LICENSE_MODEL_LOADED: + return { + ...state, + data: action.response + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js new file mode 100644 index 0000000000..8874c4ce21 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LicenseModelConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.LICENSE_MODELS_LIST_LOADED: + return [...action.response.results]; + case actionTypes.ADD_LICENSE_MODEL: + return [...state, action.licenseModel]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js new file mode 100644 index 0000000000..5982b9f8ab --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {combineReducers} from 'redux'; + +import licenseModelCreationReducer from './creation/LicenseModelCreationReducer.js'; +import licenseModelEditorReducer from './LicenseModelEditorReducer.js'; + +import licenseAgreementListReducer from './licenseAgreement/LicenseAgreementListReducer.js'; +import licenseAgreementEditorReducer from './licenseAgreement/LicenseAgreementEditorReducer.js'; +import {actionTypes as licenseAgreementActionTypes} from './licenseAgreement/LicenseAgreementConstants.js'; + +import featureGroupsEditorReducer from './featureGroups/FeatureGroupsEditorReducer.js'; +import featureGroupsListReducer from './featureGroups/FeatureGroupsListReducer.js'; +import {actionTypes as featureGroupsActionConstants} from './featureGroups/FeatureGroupsConstants'; + +import entitlementPoolsListReducer from './entitlementPools/EntitlementPoolsListReducer.js'; +import entitlementPoolsEditorReducer from './entitlementPools/EntitlementPoolsEditorReducer.js'; +import {actionTypes as entitlementPoolsConstants} from './entitlementPools/EntitlementPoolsConstants'; + +import licenseKeyGroupsEditorReducer from './licenseKeyGroups/LicenseKeyGroupsEditorReducer.js'; +import licenseKeyGroupsListReducer from './licenseKeyGroups/LicenseKeyGroupsListReducer.js'; +import {actionTypes as licenseKeyGroupsConstants} from './licenseKeyGroups/LicenseKeyGroupsConstants.js'; + +export default combineReducers({ + licenseModelCreation: licenseModelCreationReducer, + licenseModelEditor: licenseModelEditorReducer, + + licenseAgreement: combineReducers({ + licenseAgreementEditor: licenseAgreementEditorReducer, + licenseAgreementList: licenseAgreementListReducer, + licenseAgreementToDelete: (state = false, action) => action.type === licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM ? action.licenseAgreementToDelete : state + }), + featureGroup: combineReducers({ + featureGroupEditor: featureGroupsEditorReducer, + featureGroupsList: featureGroupsListReducer, + featureGroupToDelete: (state = false, action) => action.type === featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM ? action.featureGroupToDelete : state + }), + entitlementPool: combineReducers({ + entitlementPoolEditor: entitlementPoolsEditorReducer, + entitlementPoolsList: entitlementPoolsListReducer, + entitlementPoolToDelete: (state = false, action) => action.type === entitlementPoolsConstants.ENTITLEMENT_POOLS_DELETE_CONFIRM ? action.entitlementPoolToDelete : state + }), + licenseKeyGroup: combineReducers({ + licenseKeyGroupsEditor: licenseKeyGroupsEditorReducer, + licenseKeyGroupsList: licenseKeyGroupsListReducer, + licenseKeyGroupToDelete: (state = false, action) => action.type === licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM ? action.licenseKeyGroupToDelete : state + }), +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js new file mode 100644 index 0000000000..63d0f27b6a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js @@ -0,0 +1,41 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; +import LicenseModelCreationActionHelper from './LicenseModelCreationActionHelper.js'; +import LicenseModelCreationView from './LicenseModelCreationView.jsx'; + +const mapStateToProps = ({licenseModel: {licenseModelCreation}}) => licenseModelCreation; + +const mapActionsToProps = (dispatch) => { + return { + onDataChanged: deltaData => LicenseModelCreationActionHelper.dataChanged(dispatch, {deltaData}), + onCancel: () => LicenseModelCreationActionHelper.close(dispatch), + onSubmit: (licenseModel) => { + LicenseModelCreationActionHelper.close(dispatch); + LicenseModelCreationActionHelper.createLicenseModel(dispatch, {licenseModel}).then(licenseModelId => { + OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId}); + }); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(LicenseModelCreationView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js new file mode 100644 index 0000000000..c2a0409bd2 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js @@ -0,0 +1,72 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; +import {actionTypes} from './LicenseModelCreationConstants.js'; + +function baseUrl() { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/`; +} + +function createLicenseModel(licenseModel) { + return RestAPIUtil.create(baseUrl(), { + vendorName: licenseModel.vendorName, + description: licenseModel.description, + iconRef: 'icon' + }); +} + + +export default { + + open(dispatch) { + dispatch({ + type: actionTypes.OPEN + }); + }, + + close(dispatch){ + dispatch({ + type: actionTypes.CLOSE + }); + }, + + dataChanged(dispatch, {deltaData}){ + dispatch({ + type: actionTypes.DATA_CHANGED, + deltaData + }); + }, + + createLicenseModel(dispatch, {licenseModel}){ + return createLicenseModel(licenseModel).then(response => { + LicenseModelActionHelper.addLicenseModel(dispatch, { + licenseModel: { + ...licenseModel, + id: response.value + } + }); + return response.value; + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js new file mode 100644 index 0000000000..603d177048 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js @@ -0,0 +1,27 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + OPEN: null, + CLOSE: null, + DATA_CHANGED: null +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js new file mode 100644 index 0000000000..a54d1b3089 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LicenseModelCreationConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.OPEN: + return { + ...state, + data: {} + }; + case actionTypes.CLOSE: + return {}; + case actionTypes.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx new file mode 100644 index 0000000000..4dccc9e1c4 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; + +const LicenseModelPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + vendorName: React.PropTypes.string, + description: React.PropTypes.string +}); + +class LicenseModelCreationView extends React.Component { + + static propTypes = { + data: LicenseModelPropType, + onDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired + }; + + render() { + let {data = {}, onDataChanged} = this.props; + let {vendorName, description} = data; + return ( + <div> + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => this.props.onCancel() } + labledButtons={true}> + <ValidationInput + value={vendorName} + label={i18n('Vendor Name')} + ref='vendor-name' + onChange={vendorName => onDataChanged({vendorName})} + validations={{maxLength: 25, required: true}} + type='text' + className='field-section'/> + <ValidationInput + value={description} + label={i18n('Description')} + ref='description' + onChange={description => onDataChanged({description})} + validations={{maxLength: 1000, required: true}} + type='textarea' + className='field-section'/> + </ValidationForm> + </div> + ); + } + + + submit() { + const {data:licenseModel} = this.props; + this.props.onSubmit(licenseModel); + } +} + +export default LicenseModelCreationView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js new file mode 100644 index 0000000000..631597a5b0 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js @@ -0,0 +1,149 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes as entitlementPoolsActionTypes } from './EntitlementPoolsConstants.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; + +function baseUrl(licenseModelId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/entitlement-pools`; +} + +function fetchEntitlementPoolsList(licenseModelId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`); +} + +function postEntitlementPool(licenseModelId, entitlementPool) { + return RestAPIUtil.create(baseUrl(licenseModelId), { + name: entitlementPool.name, + description: entitlementPool.description, + thresholdValue: entitlementPool.thresholdValue, + thresholdUnits: entitlementPool.thresholdUnits, + entitlementMetric: entitlementPool.entitlementMetric, + increments: entitlementPool.increments, + aggregationFunction: entitlementPool.aggregationFunction, + operationalScope: entitlementPool.operationalScope, + time: entitlementPool.time, + manufacturerReferenceNumber: entitlementPool.manufacturerReferenceNumber + }); +} + + +function putEntitlementPool(licenseModelId, previousEntitlementPool, entitlementPool) { + return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${entitlementPool.id}`, { + name: entitlementPool.name, + description: entitlementPool.description, + thresholdValue: entitlementPool.thresholdValue, + thresholdUnits: entitlementPool.thresholdUnits, + entitlementMetric: entitlementPool.entitlementMetric, + increments: entitlementPool.increments, + aggregationFunction: entitlementPool.aggregationFunction, + operationalScope: entitlementPool.operationalScope, + time: entitlementPool.time, + manufacturerReferenceNumber: entitlementPool.manufacturerReferenceNumber + }); +} + +function deleteEntitlementPool(licenseModelId, entitlementPoolId) { + return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${entitlementPoolId}`); +} + + +export default { + fetchEntitlementPoolsList(dispatch, {licenseModelId, version}) { + return fetchEntitlementPoolsList(licenseModelId, version).then(response => dispatch({ + type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_LIST_LOADED, + response + })); + }, + + openEntitlementPoolsEditor(dispatch, {entitlementPool} = {}) { + dispatch({ + type: entitlementPoolsActionTypes.entitlementPoolsEditor.OPEN, + entitlementPool + }); + }, + + deleteEntitlementPool(dispatch, {licenseModelId, entitlementPoolId}) { + return deleteEntitlementPool(licenseModelId, entitlementPoolId).then(() => { + dispatch({ + type: entitlementPoolsActionTypes.DELETE_ENTITLEMENT_POOL, + entitlementPoolId + }); + }); + }, + + entitlementPoolsEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: entitlementPoolsActionTypes.entitlementPoolsEditor.DATA_CHANGED, + deltaData + }); + }, + + closeEntitlementPoolsEditor(dispatch) { + dispatch({ + type: entitlementPoolsActionTypes.entitlementPoolsEditor.CLOSE + }); + }, + + saveEntitlementPool(dispatch, {licenseModelId, previousEntitlementPool, entitlementPool}) { + if (previousEntitlementPool) { + return putEntitlementPool(licenseModelId, previousEntitlementPool, entitlementPool).then(() => { + dispatch({ + type: entitlementPoolsActionTypes.EDIT_ENTITLEMENT_POOL, + entitlementPool + }); + }); + } + else { + return postEntitlementPool(licenseModelId, entitlementPool).then(response => { + dispatch({ + type: entitlementPoolsActionTypes.ADD_ENTITLEMENT_POOL, + entitlementPool: { + ...entitlementPool, + id: response.value + } + }); + }); + } + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_DELETE_CONFIRM, + entitlementPoolToDelete: false + }); + }, + openDeleteEntitlementPoolConfirm(dispatch, {entitlementPool}) { + dispatch({ + type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_DELETE_CONFIRM, + entitlementPoolToDelete: entitlementPool + }); + }, + + switchVersion(dispatch, {licenseModelId, version}) { + LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => { + this.fetchEntitlementPoolsList(dispatch, {licenseModelId, version}); + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx new file mode 100644 index 0000000000..04f038f5f0 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +function renderMsg(entitlementPoolToDelete) { + let poolName = entitlementPoolToDelete ? entitlementPoolToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{poolName}"?', {poolName}); + let subMsg = entitlementPoolToDelete + && entitlementPoolToDelete.referencingFeatureGroups + && entitlementPoolToDelete.referencingFeatureGroups.length > 0 ? + i18n('This entitlement pool is associated with one or more feature groups') : + ''; + return ( + <div> + <p>{msg}</p> + <p>{subMsg}</p> + </div> + ); +}; + +const mapStateToProps = ({licenseModel: {entitlementPool}}, {licenseModelId}) => { + let {entitlementPoolToDelete} = entitlementPool; + const show = entitlementPoolToDelete !== false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: renderMsg(entitlementPoolToDelete), + confirmationDetails: {entitlementPoolToDelete, licenseModelId} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({entitlementPoolToDelete, licenseModelId}) => { + EntitlementPoolsActionHelper.deleteEntitlementPool(dispatch, { + licenseModelId, + entitlementPoolId: entitlementPoolToDelete.id + }); + EntitlementPoolsActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + EntitlementPoolsActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js new file mode 100644 index 0000000000..8a855076f3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js @@ -0,0 +1,112 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export const actionTypes = keyMirror({ + + ENTITLEMENT_POOLS_LIST_LOADED: null, + ADD_ENTITLEMENT_POOL: null, + EDIT_ENTITLEMENT_POOL: null, + DELETE_ENTITLEMENT_POOL: null, + ENTITLEMENT_POOLS_DELETE_CONFIRM: null, + + entitlementPoolsEditor: { + OPEN: null, + CLOSE: null, + DATA_CHANGED: null, + } + +}); + +export const enums = keyMirror({ + SELECTED_FEATURE_GROUP_TAB: { + GENERAL: 1, + ENTITLEMENT_POOLS: 2, + LICENCE_KEY_GROUPS: 3 + }, + SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: { + ASSOCIATED_ENTITLEMENT_POOLS: 1, + AVAILABLE_ENTITLEMENT_POOLS: 2 + } +}); + +export const defaultState = { + ENTITLEMENT_POOLS_EDITOR_DATA: { + entitlementMetric: {choice: '', other: ''}, + aggregationFunction: {choice: '', other: ''}, + operationalScope: {choices: [], other: ''}, + time: {choice: '', other: ''} + } +}; + +export const thresholdUnitType = { + ABSOLUTE: 'Absolute', + PERCENTAGE: 'Percentage' +}; + +export const optionsInputValues = { + OPERATIONAL_SCOPE: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Network_Wide', title: 'Network Wide'}, + {enum: 'Availability_Zone', title: 'Availability Zone'}, + {enum: 'Data_Center', title: 'Data Center'}, + {enum: 'Tenant', title: 'Tenant'}, + {enum: 'VM', title: 'VM'}, + {enum: 'CPU', title: 'CPU'}, + {enum: 'Core', title: 'Core'} + ], + TIME: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Hour', title: 'Hour'}, + {enum: 'Day', title: 'Day'}, + {enum: 'Month', title: 'Month'} + ], + AGGREGATE_FUNCTION: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Peak', title: 'Peak'}, + {enum: 'Average', title: 'Average'} + ], + ENTITLEMENT_METRIC: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Software_Instances_Count', title: 'Software Instances'}, + {enum: 'Core', title: 'Core'}, + {enum: 'CPU', title: 'CPU'}, + {enum: 'Trunks', title: 'Trunks'}, + {enum: 'User', title: 'User'}, + {enum: 'Subscribers', title: 'Subscribers'}, + {enum: 'Tenants', title: 'Tenants'}, + {enum: 'Tokens', title: 'Tokens'}, + {enum: 'Seats', title: 'Seats'}, + {enum: 'Units_TB', title: 'Units-TB'}, + {enum: 'Units_GB', title: 'Units-GB'}, + {enum: 'Units_MB', title: 'Units-MB'} + ], + THRESHOLD_UNITS: [ + {enum: '', title: i18n('please select…')}, + {enum: thresholdUnitType.ABSOLUTE, title: 'Absolute'}, + {enum: thresholdUnitType.PERCENTAGE, title: '%'} + ] +}; + + + + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js new file mode 100644 index 0000000000..d5bd07e929 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js'; +import EntitlementPoolsEditorView from './EntitlementPoolsEditorView.jsx'; + +const mapStateToProps = ({licenseModel: {entitlementPool}}) => { + + + let {data} = entitlementPool.entitlementPoolEditor; + + let previousData; + const entitlementPoolId = data ? data.id : null; + if(entitlementPoolId) { + previousData = entitlementPool.entitlementPoolsList.find(entitlementPool => entitlementPool.id === entitlementPoolId); + } + + return { + data, + previousData + }; +}; + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onDataChanged: deltaData => EntitlementPoolsActionHelper.entitlementPoolsEditorDataChanged(dispatch, {deltaData}), + onCancel: () => EntitlementPoolsActionHelper.closeEntitlementPoolsEditor(dispatch), + onSubmit: ({previousEntitlementPool, entitlementPool}) => { + EntitlementPoolsActionHelper.closeEntitlementPoolsEditor(dispatch); + EntitlementPoolsActionHelper.saveEntitlementPool(dispatch, {licenseModelId, previousEntitlementPool, entitlementPool}); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(EntitlementPoolsEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js new file mode 100644 index 0000000000..86e97ecf8d --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes, defaultState} from './EntitlementPoolsConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.entitlementPoolsEditor.OPEN: + return { + ...state, + data: action.entitlementPool ? {...action.entitlementPool} : defaultState.ENTITLEMENT_POOLS_EDITOR_DATA + }; + case actionTypes.entitlementPoolsEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + case actionTypes.entitlementPoolsEditor.CLOSE: + return {}; + default: + return state; + } + +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx new file mode 100644 index 0000000000..77c5a12e03 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx @@ -0,0 +1,167 @@ +import React from 'react'; + + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import {optionsInputValues as EntitlementPoolsOptionsInputValues, thresholdUnitType} from './EntitlementPoolsConstants.js'; +import {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx'; + + +const EntitlementPoolPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + manufacturerReferenceNumber: React.PropTypes.string, + operationalScope: React.PropTypes.shape({ + choices: React.PropTypes.array, + other: React.PropTypes.string + }), + aggregationFunction: React.PropTypes.shape({ + choice: React.PropTypes.string, + other: React.PropTypes.string + }), + increments: React.PropTypes.string, + time: React.PropTypes.shape({ + choice: React.PropTypes.string, + other: React.PropTypes.string + }), + entitlementMetric: React.PropTypes.shape({ + choice: React.PropTypes.string, + other: React.PropTypes.string + }) +}); + +class EntitlementPoolsEditorView extends React.Component { + + static propTypes = { + data: EntitlementPoolPropType, + previousData: EntitlementPoolPropType, + isReadOnlyMode: React.PropTypes.bool, + onDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired + }; + + static defaultProps = { + data: {} + }; + + render() { + let {data = {}, onDataChanged, isReadOnlyMode} = this.props; + let { + name, description, manufacturerReferenceNumber, operationalScope, aggregationFunction, thresholdUnits, thresholdValue, + increments, time, entitlementMetric} = data; + let thresholdValueValidation = thresholdUnits === thresholdUnitType.PERCENTAGE ? {numeric: true, required: true, maxValue: 100} : {numeric: true, required: true}; + let timeValidation = time && time.choice === optionInputOther.OTHER ? {numeric: true, required: true} : {required: true}; + + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => this.props.onCancel() } + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + className='entitlement-pools-form'> + <div className='entitlement-pools-form-row'> + <ValidationInput + onChange={name => onDataChanged({name})} + label={i18n('Name')} + value={name} + validations={{maxLength: 120, required: true}} + type='text'/> + + <ValidationInput + isMultiSelect={true} + onEnumChange={operationalScope => onDataChanged({operationalScope:{choices: operationalScope, other: ''}})} + onOtherChange={operationalScope => onDataChanged({operationalScope:{choices: [optionInputOther.OTHER], other: operationalScope}})} + multiSelectedEnum={operationalScope && operationalScope.choices} + label={i18n('Operational Scope')} + otherValue={operationalScope && operationalScope.other} + validations={{required: true}} + values={EntitlementPoolsOptionsInputValues.OPERATIONAL_SCOPE}/> + + </div> + <div className='entitlement-pools-form-row'> + <ValidationInput + onChange={description => onDataChanged({description})} + label={i18n('Description')} + value={description} + validations={{maxLength: 1000, required: true}} + type='textarea'/> + <div className='entitlement-pools-form-row-group'> + <div className='entitlement-pools-form-row'> + <ValidationInput + onEnumChange={thresholdUnits => onDataChanged({thresholdUnits})} + selectedEnum={thresholdUnits} + label={i18n('Threshold Value')} + type='select' + values={EntitlementPoolsOptionsInputValues.THRESHOLD_UNITS} + validations={{required: true}}/> + <ValidationInput + className='entitlement-pools-form-row-threshold-value' + onChange={thresholdValue => onDataChanged({thresholdValue})} + value={thresholdValue} + validations={thresholdValueValidation} + type='text'/> + </div> + + <ValidationInput + onEnumChange={entitlementMetric => onDataChanged({entitlementMetric:{choice: entitlementMetric, other: ''}})} + onOtherChange={entitlementMetric => onDataChanged({entitlementMetric:{choice: optionInputOther.OTHER, other: entitlementMetric}})} + selectedEnum={entitlementMetric && entitlementMetric.choice} + otherValue={entitlementMetric && entitlementMetric.other} + label={i18n('Entitlement Metric')} + validations={{required: true}} + values={EntitlementPoolsOptionsInputValues.ENTITLEMENT_METRIC}/> + <ValidationInput + onEnumChange={aggregationFunction => onDataChanged({aggregationFunction:{choice: aggregationFunction, other: ''}})} + onOtherChange={aggregationFunction => onDataChanged({aggregationFunction:{choice: optionInputOther.OTHER, other: aggregationFunction}})} + selectedEnum={aggregationFunction && aggregationFunction.choice} + otherValue={aggregationFunction && aggregationFunction.other} + validations={{required: true}} + label={i18n('Aggregate Function')} + values={EntitlementPoolsOptionsInputValues.AGGREGATE_FUNCTION}/> + + </div> + </div> + <div className='entitlement-pools-form-row'> + + <ValidationInput + onChange={manufacturerReferenceNumber => onDataChanged({manufacturerReferenceNumber})} + label={i18n('Manufacturer Reference Number')} + value={manufacturerReferenceNumber} + validations={{maxLength: 100, required: true}} + type='text'/> + + <ValidationInput + onEnumChange={time => onDataChanged({time:{choice: time, other: ''}})} + onOtherChange={time => onDataChanged({time:{choice: optionInputOther.OTHER, other: time}})} + selectedEnum={time && time.choice} + otherValue={time && time.other} + validations={timeValidation} + label={i18n('Time')} + values={EntitlementPoolsOptionsInputValues.TIME}/> + </div> + <div className='entitlement-pools-form-row'> + <ValidationInput + onChange={increments => onDataChanged({increments})} + label={i18n('Increments')} + value={increments} + validations={{maxLength: 120}} + type='text'/> + + </div> + </ValidationForm> + ); + } + + submit() { + const {data: entitlementPool, previousData: previousEntitlementPool} = this.props; + this.props.onSubmit({entitlementPool, previousEntitlementPool}); + } +} + +export default EntitlementPoolsEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js new file mode 100644 index 0000000000..4b21a2fea8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js'; +import EntitlementPoolsListEditorView from './EntitlementPoolsListEditorView.jsx'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +const mapStateToProps = ({licenseModel: {entitlementPool, licenseModelEditor}}) => { + let {entitlementPoolsList} = entitlementPool; + let {data} = entitlementPool.entitlementPoolEditor; + + let {vendorName} = licenseModelEditor.data; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data); + + return { + vendorName, + entitlementPoolsList, + isReadOnlyMode, + isDisplayModal: Boolean(data), + isModalInEditMode: Boolean(data && data.id), + }; +}; + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onAddEntitlementPoolClick: () => EntitlementPoolsActionHelper.openEntitlementPoolsEditor(dispatch), + onEditEntitlementPoolClick: entitlementPool => EntitlementPoolsActionHelper.openEntitlementPoolsEditor(dispatch, {entitlementPool}), + onDeleteEntitlementPool: entitlementPool => EntitlementPoolsActionHelper.openDeleteEntitlementPoolConfirm(dispatch, { + licenseModelId, + entitlementPool + }) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(EntitlementPoolsListEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx new file mode 100644 index 0000000000..52df102503 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx @@ -0,0 +1,132 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import EntitlementPoolsEditor from './EntitlementPoolsEditor.js'; +import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx'; +import {optionsInputValues} from './EntitlementPoolsConstants'; +import EntitlementPoolsConfirmationModal from './EntitlementPoolsConfirmationModal.jsx'; + + +class EntitlementPoolsListEditorView extends React.Component { + static propTypes = { + vendorName: React.PropTypes.string, + licenseModelId: React.PropTypes.string.isRequired, + entitlementPoolsList: React.PropTypes.array, + isReadOnlyMode: React.PropTypes.bool.isRequired, + isDisplayModal: React.PropTypes.bool, + isModalInEditMode: React.PropTypes.bool, + onAddEntitlementPoolClick: React.PropTypes.func, + onEditEntitlementPoolClick: React.PropTypes.func, + onDeleteEntitlementPool: React.PropTypes.func, + }; + + static defaultProps = { + entitlementPoolsList: [] + }; + + state = { + localFilter: '' + }; + + render() { + let {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props; + let {onAddEntitlementPoolClick} = this.props; + const {localFilter} = this.state; + + return ( + <div className='entitlement-pools-list-editor'> + <ListEditorView + title={i18n('Entitlement Pools for {vendorName} License Model', {vendorName})} + plusButtonTitle={i18n('Add Entitlement Pool')} + onAdd={onAddEntitlementPoolClick} + filterValue={localFilter} + onFilter={filter => this.setState({localFilter: filter})} + isReadOnlyMode={isReadOnlyMode}> + {this.filterList().map(entitlementPool => this.renderEntitlementPoolListItem(entitlementPool, isReadOnlyMode))} + </ListEditorView> + <Modal show={isDisplayModal} bsSize='large' animation={true} className='entitlement-pools-modal'> + <Modal.Header> + <Modal.Title>{`${isModalInEditMode ? i18n('Edit Entitlement Pool') : i18n('Create New Entitlement Pool')}`}</Modal.Title> + </Modal.Header> + <Modal.Body> + { + isDisplayModal && ( + <EntitlementPoolsEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode}/> + ) + } + </Modal.Body> + </Modal> + + <EntitlementPoolsConfirmationModal licenseModelId={licenseModelId}/> + </div> + ); + } + + filterList() { + let {entitlementPoolsList} = this.props; + let {localFilter} = this.state; + if(localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return entitlementPoolsList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return entitlementPoolsList; + } + } + + renderEntitlementPoolListItem(entitlementPool, isReadOnlyMode) { + let {id, name, description, thresholdValue, thresholdUnits, entitlementMetric, aggregationFunction, + manufacturerReferenceNumber, time} = entitlementPool; + let {onEditEntitlementPoolClick, onDeleteEntitlementPool} = this.props; + return ( + <ListEditorItemView + key={id} + onSelect={() => onEditEntitlementPoolClick(entitlementPool)} + onDelete={() => onDeleteEntitlementPool(entitlementPool)} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='text name'>{name}</div> + </div> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Entitlement')}</div> + <div className='entitlement-parameters'>{`${this.extractValue(aggregationFunction)} ${this.extractValue(entitlementMetric)} per ${this.extractValue(time)}`}</div> + <div className='entitlement-pools-count'>{`${thresholdValue ? thresholdValue : ''} ${this.extractUnits(thresholdUnits)}`}</div> + </div> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Manufacturer Reference Number')}</div> + <div className='text contract-number'>{manufacturerReferenceNumber}</div> + </div> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='text description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + + + extractUnits(units) { + if (units === undefined) {return '';} //TODO fix it later + return units === 'Absolute' ? '' : '%'; + } + + extractValue(item) { + if (item === undefined) {return '';} //TODO fix it later + + return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : ''; + } +} + +export default EntitlementPoolsListEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js new file mode 100644 index 0000000000..63e351fce7 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './EntitlementPoolsConstants'; +export default (state = [], action) => { + switch (action.type) { + case actionTypes.ENTITLEMENT_POOLS_LIST_LOADED: + return [...action.response.results]; + case actionTypes.ADD_ENTITLEMENT_POOL: + return [...state, action.entitlementPool]; + case actionTypes.EDIT_ENTITLEMENT_POOL: + const indexForEdit = state.findIndex(entitlementPool => entitlementPool.id === action.entitlementPool.id); + return [...state.slice(0, indexForEdit), action.entitlementPool, ...state.slice(indexForEdit + 1)]; + case actionTypes.DELETE_ENTITLEMENT_POOL: + return state.filter(entitlementPool => entitlementPool.id !== action.entitlementPoolId); + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js new file mode 100644 index 0000000000..c2b269bcf9 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js'; +import FeatureGroupEditorView from './FeatureGroupEditorView.jsx'; + +const mapStateToProps = ({licenseModel: {featureGroup, entitlementPool, licenseKeyGroup}}) => { + + const {featureGroupEditor} = featureGroup; + + let {data, selectedTab, selectedEntitlementPoolsButtonTab, selectedLicenseKeyGroupsButtonTab} = featureGroupEditor; + + let previousData; + const featureGroupId = data ? data.id : null; + if (featureGroupId) { + previousData = featureGroup.featureGroupsList.find(featureGroup => featureGroup.id === featureGroupId); + } + let {entitlementPoolsList = []} = entitlementPool; + let {licenseKeyGroupsList = []} = licenseKeyGroup; + + return { + data, + previousData, + selectedTab, + selectedEntitlementPoolsButtonTab, + selectedLicenseKeyGroupsButtonTab, + entitlementPoolsList, + licenseKeyGroupsList + }; +}; + + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onTabSelect: tab => FeatureGroupsActionHelper.selectEntitlementPoolsEditorTab(dispatch, {tab}), + onEntitlementPoolsButtonTabSelect: buttonTab => FeatureGroupsActionHelper.selectFeatureGroupsEditorEntitlementPoolsButtonTab(dispatch, {buttonTab}), + onLicenseKeyGroupsButtonTabSelect: buttonTab => FeatureGroupsActionHelper.selectFeatureGroupsEditorLicenseKeyGroupsButtonTab(dispatch, {buttonTab}), + onDataChanged: deltaData => FeatureGroupsActionHelper.featureGroupsEditorDataChanged(dispatch, {deltaData}), + onSubmit: (previousFeatureGroup, featureGroup) => { + FeatureGroupsActionHelper.closeFeatureGroupsEditor(dispatch); + FeatureGroupsActionHelper.saveFeatureGroup(dispatch, {licenseModelId, previousFeatureGroup, featureGroup}); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(FeatureGroupEditorView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx new file mode 100644 index 0000000000..6fecd16b71 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx @@ -0,0 +1,339 @@ +import React from 'react'; +import ValidationTabs from 'nfvo-components/input/validation/ValidationTabs.jsx'; +import ValidationTab from 'nfvo-components/input/validation/ValidationTab.jsx'; +import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js'; +import Button from 'react-bootstrap/lib/Button.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import DualListboxView from 'nfvo-components/input/dualListbox/DualListboxView.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorViewItem from 'nfvo-components/listEditor/ListEditorItemView.jsx'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import {state as FeatureGroupStateConstants} from './FeatureGroupsConstants.js'; + +const FeatureGroupsPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + partNumber: React.PropTypes.string, + entitlementPoolsIds: React.PropTypes.array(React.PropTypes.string), + licenseKeyGroupsIds: React.PropTypes.array(React.PropTypes.string) +}); + +class FeatureGroupEditorView extends React.Component { + + + static propTypes = { + data: FeatureGroupsPropType, + previousData: FeatureGroupsPropType, + isReadOnlyMode: React.PropTypes.bool, + + onSubmit: React.PropTypes.func, + onCancel: React.PropTypes.func, + + selectedTab: React.PropTypes.number, + onTabSelect: React.PropTypes.func, + + selectedEntitlementPoolsButtonTab: React.PropTypes.number, + selectedLicenseKeyGroupsButtonTab: React.PropTypes.number, + onEntitlementPoolsButtonTabSelect: React.PropTypes.func, + onLicenseKeyGroupsButtonTabSelect: React.PropTypes.func, + + entitlementPoolsList: DualListboxView.propTypes.availableList, + licenseKeyGroupsList: DualListboxView.propTypes.availableList + }; + + + static defaultProps = { + data: {}, + selectedTab: FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.GENERAL, + selectedEntitlementPoolsButtonTab: FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS, + selectedLicenseKeyGroupsButtonTab: FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS + }; + + state = { + localEntitlementPoolsListFilter: '', + localLicenseKeyGroupsListFilter: '' + }; + + + render() { + let {selectedTab, onTabSelect, isReadOnlyMode} = this.props; + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => this.props.onCancel() } + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + name='feature-group-validation-form' + className='feature-group-form'> + <ValidationTabs activeKey={onTabSelect ? selectedTab : undefined} onSelect={onTabSelect}> + {this.renderGeneralTab()} + {this.renderEntitlementPoolsTab()} + {this.renderLicenseKeyGroupsTab()} + </ValidationTabs> + + </ValidationForm> + ); + } + + submit() { + const {data: featureGroup, previousData: previousFeatureGroup} = this.props; + this.props.onSubmit(previousFeatureGroup, featureGroup); + } + + renderGeneralTab() { + let {data = {}, onDataChanged} = this.props; + let {name, description, partNumber} = data; + return ( + <ValidationTab eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.GENERAL} title={i18n('General')}> + <div> + <ValidationInput + groupClassName='field-section' + onChange={name => onDataChanged({name})} + ref='name' + label={i18n('Name')} + value={name} + name='feature-group-name' + validations={{maxLength: 120, required: true}} + type='text'/> + <ValidationInput + groupClassName='field-section' + className='description-field' + onChange={description => onDataChanged({description})} + ref='description' + label={i18n('Description')} + value={description} + name='feature-group-description' + validations={{maxLength: 1000, required: true}} + type='textarea'/> + <ValidationInput + groupClassName='field-section' + onChange={partNumber => onDataChanged({partNumber})} + label={i18n('Part Number')} + value={partNumber} + validations={{required: true}} + type='text'/> + </div> + </ValidationTab> + ); + } + + renderEntitlementPoolsTab() { + let {selectedEntitlementPoolsButtonTab, onEntitlementPoolsButtonTabSelect, entitlementPoolsList} = this.props; + if (entitlementPoolsList.length > 0) { + return ( + <ValidationTab + eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS} + title={i18n('Entitlement Pools')}> + <ButtonGroup> + { + this.renderButtonsTab( + FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS, + selectedEntitlementPoolsButtonTab, + i18n('Associated Entitlement Pools'), + onEntitlementPoolsButtonTabSelect + ) + } + { + this.renderButtonsTab( + FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.AVAILABLE_ENTITLEMENT_POOLS, + selectedEntitlementPoolsButtonTab, + i18n('Available Entitlement Pools'), + onEntitlementPoolsButtonTabSelect + ) + } + </ButtonGroup> + {this.renderEntitlementPoolsButtonTabContent(selectedEntitlementPoolsButtonTab)} + </ValidationTab> + ); + } else { + return ( + <ValidationTab + eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS} + title={i18n('Entitlement Pools')}> + <p>{i18n('There is no available entitlement pools.')}</p> + </ValidationTab> + ); + } + } + + renderLicenseKeyGroupsTab() { + let {selectedLicenseKeyGroupsButtonTab, onLicenseKeyGroupsButtonTabSelect, licenseKeyGroupsList} = this.props; + if (licenseKeyGroupsList.length > 0) { + return ( + <ValidationTab + eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS} + title={i18n('License Key Groups')}> + <ButtonGroup> + { + this.renderButtonsTab( + FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS, + selectedLicenseKeyGroupsButtonTab, + i18n('Associated License Key Groups'), + onLicenseKeyGroupsButtonTabSelect + ) + } + { + this.renderButtonsTab( + FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.AVAILABLE_LICENSE_KEY_GROUPS, + selectedLicenseKeyGroupsButtonTab, + i18n('Available License Key Groups'), + onLicenseKeyGroupsButtonTabSelect + ) + } + </ButtonGroup> + {this.renderLicenseKeyGroupsTabContent(selectedLicenseKeyGroupsButtonTab)} + </ValidationTab> + ); + } else { + return ( + <ValidationTab + eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS} + title={i18n('License Key Groups')}> + <p>{i18n('There is no available license key groups')}</p> + </ValidationTab>); + } + } + + renderButtonsTab(buttonTab, selectedButtonTab, title, onClick) { + const isSelected = buttonTab === selectedButtonTab; + return ( + <Button + className='button-tab' + active={isSelected} + onClick={() => !isSelected && onClick(buttonTab)}> + { title } + </Button> + ); + } + + renderEntitlementPoolsButtonTabContent(selectedFeatureGroupsButtonTab) { + const {entitlementPoolsList = [], data: {entitlementPoolsIds = []}} = this.props; + let dualBoxTitle = { + left: i18n('Available Entitlement Pools'), + right: i18n('Selected Entitlement Pools') + }; + + if (entitlementPoolsList.length) { + const {localEntitlementPoolsListFilter} = this.state; + let selectedEntitlementPools = entitlementPoolsIds.map(entitlementPoolId => entitlementPoolsList.find(entitlementPool => entitlementPool.id === entitlementPoolId)); + + switch (selectedFeatureGroupsButtonTab) { + case FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS: + if (selectedEntitlementPools.length) { + return ( + <ListEditorView + className='thinner-list' + filterValue={localEntitlementPoolsListFilter} + onFilter={localEntitlementPoolsListFilter => this.setState({localEntitlementPoolsListFilter})}> + {this.filterAssociatedItems(selectedEntitlementPools, localEntitlementPoolsListFilter) + .map(entitlementPool => this.renderAssociatedListItem(entitlementPool + , FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS))} + </ListEditorView> + ); + } + else { + return ( + <div> + <br/>{i18n('There are currently no entitlement pools associated with this feature group. Click "Available Entitlement Pools" to associate.')} + </div> + ); + } + case FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.AVAILABLE_ENTITLEMENT_POOLS: + return ( + <DualListboxView + filterTitle={dualBoxTitle} + selectedValuesList={entitlementPoolsIds} + availableList={entitlementPoolsList} + onChange={ selectedValuesList => this.props.onDataChanged( { entitlementPoolsIds: selectedValuesList } )}/> + ); + } + } + } + + renderLicenseKeyGroupsTabContent(selectedFeatureGroupsButtonTab) { + const {licenseKeyGroupsList = [], data: {licenseKeyGroupsIds = []}} = this.props; + let dualBoxFilterTitle = { + left: i18n('Available License Key Groups'), + right: i18n('Selected License Key Groups') + }; + + if (licenseKeyGroupsList.length) { + const {localLicenseKeyGroupsListFilter} = this.state; + let selectedLicenseKeyGroups = licenseKeyGroupsIds.map(licenseKeyGroupId => licenseKeyGroupsList.find(licenseKeyGroup => licenseKeyGroup.id === licenseKeyGroupId)); + + switch (selectedFeatureGroupsButtonTab) { + case FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS: + if (selectedLicenseKeyGroups.length) { + return ( + <ListEditorView + className='thinner-list' + filterValue={localLicenseKeyGroupsListFilter} + onFilter={localLicenseKeyGroupsListFilter => this.setState({localLicenseKeyGroupsListFilter})}> + {this.filterAssociatedItems(selectedLicenseKeyGroups, localLicenseKeyGroupsListFilter) + .map(licenseKeyGroup => this.renderAssociatedListItem(licenseKeyGroup + , FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS))} + </ListEditorView> + ); + } else { + return ( + <div className='no-items-msg'> + {i18n('There are currently no license key groups associated with this feature group. Click "Available License Key Groups" to associate.')} + </div> + ); + } + case FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.AVAILABLE_LICENSE_KEY_GROUPS: + return ( + <DualListboxView + filterTitle={dualBoxFilterTitle} + selectedValuesList={this.props.data.licenseKeyGroupsIds} + availableList={this.props.licenseKeyGroupsList} + onChange={ selectedValuesList => this.props.onDataChanged( { licenseKeyGroupsIds: selectedValuesList } )}/> + ); + } + } + } + + + renderAssociatedListItem(listItem, itemType) { + let {isReadOnlyMode} = this.props; + return ( + <ListEditorViewItem + key={listItem.id} + onDelete={() => this.deleteAssociatedItem(listItem.id, itemType)} + isReadOnlyMode={isReadOnlyMode}> + <div className='name'>{listItem.name}</div> + <div className='description'>{listItem.description}</div> + </ListEditorViewItem> + ); + } + + filterAssociatedItems(list, localList) { + if (localList) { + const filter = new RegExp(escape(localList), 'i'); + return list.filter(({name = '', description = ''}) => name.match(filter) || description.match(filter)); + } + else { + return list; + } + } + + deleteAssociatedItem(id, type) { + const {data: {licenseKeyGroupsIds = [], entitlementPoolsIds = []}} = this.props; + if (type === FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS) { + this.props.onDataChanged({licenseKeyGroupsIds: licenseKeyGroupsIds.filter(listId => listId !== id)}); + } else { + this.props.onDataChanged({entitlementPoolsIds: entitlementPoolsIds.filter(listId => listId !== id)}); + } + + } +} + + +export default FeatureGroupEditorView; + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js new file mode 100644 index 0000000000..9ea5a31490 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js @@ -0,0 +1,56 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js'; +import FeatureGroupListEditorView from './FeatureGroupListEditorView.jsx'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +const mapStateToProps = ({licenseModel: {featureGroup, licenseModelEditor}}) => { + const {featureGroupEditor: {data}, featureGroupsList} = featureGroup; + let {vendorName} = licenseModelEditor.data; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data); + + return { + vendorName, + featureGroupsModal: { + show: Boolean(data), + editMode: Boolean(data && data.id) + }, + featureGroupsList, + isReadOnlyMode + }; +}; + + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onDeleteFeatureGroupClick: (featureGroup) => FeatureGroupsActionHelper.openDeleteFeatureGroupConfirm(dispatch, {licenseModelId, featureGroup}), + onCancelFeatureGroupsEditor: () => FeatureGroupsActionHelper.closeFeatureGroupsEditor(dispatch), + + onAddFeatureGroupClick: () => FeatureGroupsActionHelper.openFeatureGroupsEditor(dispatch, {licenseModelId}), + onEditFeatureGroupClick: featureGroup => FeatureGroupsActionHelper.openFeatureGroupsEditor(dispatch, { + featureGroup, + licenseModelId + }) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(FeatureGroupListEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx new file mode 100644 index 0000000000..d998f9216f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx @@ -0,0 +1,136 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import FeatureGroupEditor from './FeatureGroupEditor.js'; +import FeatureGroupsConfirmationModal from './FeatureGroupsConfirmationModal.jsx'; + +class FeatureGroupListEditorView extends React.Component { + static propTypes = { + vendorName: React.PropTypes.string, + licenseModelId: React.PropTypes.string.isRequired, + featureGroupsModal: React.PropTypes.shape({ + show: React.PropTypes.bool, + editMode: React.PropTypes.bool + }), + isReadOnlyMode: React.PropTypes.bool.isRequired, + onAddFeatureGroupClick: React.PropTypes.func, + onEditFeatureGroupClick: React.PropTypes.func, + onDeleteFeatureGroupClick: React.PropTypes.func, + onCancelFeatureGroupsEditor: React.PropTypes.func, + featureGroupsList: React.PropTypes.array + }; + + static defaultProps = { + featureGroupsList: [], + featureGroupsModal: { + show: false, + editMode: false + } + }; + + state = { + localFilter: '' + }; + + render() { + let {vendorName, licenseModelId, featureGroupsModal, isReadOnlyMode, onAddFeatureGroupClick} = this.props; + const {localFilter} = this.state; + + return ( + <div className='feature-groups-list-editor'> + <ListEditorView + title={i18n('Feature Groups for {vendorName} License Model', {vendorName})} + plusButtonTitle={i18n('Add Feature Group')} + filterValue={localFilter} + onFilter={filter => this.setState({localFilter: filter})} + onAdd={() => onAddFeatureGroupClick()} + isReadOnlyMode={isReadOnlyMode}> + {this.filterList().map(listItem => this.renderFeatureGroupListItem(listItem, isReadOnlyMode))} + </ListEditorView> + <Modal show={featureGroupsModal.show} bsSize='large' animation={true} className='feature-group-modal'> + <Modal.Header> + <Modal.Title>{`${featureGroupsModal.editMode ? i18n('Edit Feature Group') : i18n('Create New Feature Group')}`}</Modal.Title> + </Modal.Header> + <Modal.Body> + <FeatureGroupEditor + onCancel={() => this.closeFeatureGroupsEditor()} + licenseModelId={licenseModelId} + isReadOnlyMode={isReadOnlyMode}/> + </Modal.Body> + </Modal> + + <FeatureGroupsConfirmationModal licenseModelId={licenseModelId}/> + + </div> + ); + } + + + renderFeatureGroupListItem(listItem, isReadOnlyMode) { + let {name, description, entitlementPoolsIds = [], licenseKeyGroupsIds = []} = listItem; + return ( + <ListEditorItemView + key={listItem.id} + onDelete={() => this.deleteFeatureGroupItem(listItem)} + onSelect={() => this.editFeatureGroupItem(listItem)} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='text name'>{name}</div> + </div> + + <div className='list-editor-item-view-field'> + <div className='feature-groups-count-field'> + <div className='title'>{i18n('Entitlement')}</div> + <div className='title'>{i18n('Pools')}</div> + <div className='feature-groups-count-ep'>{entitlementPoolsIds.length || 0}</div> + </div> + <div className='feature-groups-count-field'> + <div className='title'>{i18n('License key')}</div> + <div className='title'>{i18n('Groups')}</div> + <div className='feature-groups-count-lk'>{licenseKeyGroupsIds.length || 0}</div> + </div> + </div> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='text description'>{description}</div> + </div> + + </ListEditorItemView> + ); + } + + filterList() { + let {featureGroupsList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return featureGroupsList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return featureGroupsList; + } + } + + closeFeatureGroupsEditor() { + this.props.onCancelFeatureGroupsEditor(); + } + + editFeatureGroupItem(featureGroup) { + this.props.onEditFeatureGroupClick(featureGroup); + } + + deleteFeatureGroupItem(featureGroup) { + this.props.onDeleteFeatureGroupClick(featureGroup); + } +} + +export default FeatureGroupListEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js new file mode 100644 index 0000000000..3776c01263 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js @@ -0,0 +1,165 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes as featureGroupsActionConstants} from './FeatureGroupsConstants.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; +import EntitlementPoolsActionHelper from 'sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js'; +import LicenseKeyGroupsActionHelper from 'sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js'; + +function baseUrl(licenseModelId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/feature-groups`; +} + +function fetchFeatureGroupsList(licenseModelId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`); +} + +function deleteFeatureGroup(licenseModelId, featureGroupId) { + return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${featureGroupId}`); +} + +function addFeatureGroup(licenseModelId, featureGroup) { + return RestAPIUtil.create(baseUrl(licenseModelId), { + name: featureGroup.name, + description: featureGroup.description, + partNumber: featureGroup.partNumber, + addedLicenseKeyGroupsIds: featureGroup.licenseKeyGroupsIds, + addedEntitlementPoolsIds: featureGroup.entitlementPoolsIds + }); +} + +function updateFeatureGroup(licenseModelId, previousFeatureGroup, featureGroup) { + + const {licenseKeyGroupsIds = []} = featureGroup; + const {licenseKeyGroupsIds: prevLicenseKeyGroupsIds = []} = previousFeatureGroup; + const {entitlementPoolsIds = []} = featureGroup; + const {entitlementPoolsIds: prevEntitlementPoolsIds = []} = previousFeatureGroup; + return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${featureGroup.id}`, { + name: featureGroup.name, + description: featureGroup.description, + partNumber: featureGroup.partNumber, + addedLicenseKeyGroupsIds: licenseKeyGroupsIds.filter(licenseKeyGroupId => prevLicenseKeyGroupsIds.indexOf(licenseKeyGroupId) === -1), + removedLicenseKeyGroupsIds: prevLicenseKeyGroupsIds.filter(prevLicenseKeyGroupId => licenseKeyGroupsIds.indexOf(prevLicenseKeyGroupId) === -1), + addedEntitlementPoolsIds: entitlementPoolsIds.filter(entitlementPoolId => prevEntitlementPoolsIds.indexOf(entitlementPoolId) === -1), + removedEntitlementPoolsIds: prevEntitlementPoolsIds.filter(prevEntitlementPoolId => entitlementPoolsIds.indexOf(prevEntitlementPoolId) === -1) + + }); +} + +export default { + fetchFeatureGroupsList(dispatch, {licenseModelId, version}) { + return fetchFeatureGroupsList(licenseModelId, version).then(response => dispatch({ + type: featureGroupsActionConstants.FEATURE_GROUPS_LIST_LOADED, + response + })); + }, + + deleteFeatureGroup(dispatch, {licenseModelId, featureGroupId}) { + return deleteFeatureGroup(licenseModelId, featureGroupId).then(() => dispatch({ + type: featureGroupsActionConstants.DELETE_FEATURE_GROUPS, + featureGroupId + })); + }, + + saveFeatureGroup(dispatch, {licenseModelId, previousFeatureGroup, featureGroup}) { + if (previousFeatureGroup) { + return updateFeatureGroup(licenseModelId, previousFeatureGroup, featureGroup).then(() => dispatch({ + type: featureGroupsActionConstants.EDIT_FEATURE_GROUPS, + featureGroup + })); + } + else { + return addFeatureGroup(licenseModelId, featureGroup).then(response => dispatch({ + type: featureGroupsActionConstants.ADD_FEATURE_GROUPS, + featureGroup: { + ...featureGroup, + id: response.value + } + })); + } + }, + + selectEntitlementPoolsEditorTab(dispatch, {tab}) { + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.SELECT_TAB, + tab + }); + }, + + selectFeatureGroupsEditorEntitlementPoolsButtonTab(dispatch, {buttonTab}) { + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB, + buttonTab + }); + }, + + selectFeatureGroupsEditorLicenseKeyGroupsButtonTab(dispatch, {buttonTab}) { + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB, + buttonTab + }); + }, + + openFeatureGroupsEditor(dispatch, {featureGroup, licenseModelId}) { + EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId}); + LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId}); + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.OPEN, + featureGroup + }); + }, + + closeFeatureGroupsEditor(dispatch) { + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.CLOSE + }); + }, + + featureGroupsEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: featureGroupsActionConstants.featureGroupsEditor.DATA_CHANGED, + deltaData + }); + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM, + featureGroupToDelete: false + }); + }, + + openDeleteFeatureGroupConfirm(dispatch, {featureGroup}) { + dispatch({ + type: featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM, + featureGroupToDelete: featureGroup + }); + }, + + switchVersion(dispatch, {licenseModelId, version}) { + LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => { + this.fetchFeatureGroupsList(dispatch, {licenseModelId, version}); + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx new file mode 100644 index 0000000000..142ec3c4c8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +function renderMsg(featureGroupToDelete) { + let name = featureGroupToDelete ? featureGroupToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{name}"?', {name}); + let subMsg = featureGroupToDelete + && featureGroupToDelete.referencingLicenseAgreements + && featureGroupToDelete.referencingLicenseAgreements.length > 0 ? + i18n('This feature group is associated with one ore more license agreements') : + ''; + return ( + <div> + <p>{msg}</p> + <p>{subMsg}</p> + </div> + ); +}; + +const mapStateToProps = ({licenseModel: {featureGroup}}, {licenseModelId}) => { + let {featureGroupToDelete} = featureGroup; + const show = featureGroupToDelete !== false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: renderMsg(featureGroupToDelete), + confirmationDetails: {featureGroupToDelete, licenseModelId} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({featureGroupToDelete, licenseModelId}) => { + FeatureGroupsActionHelper.deleteFeatureGroup(dispatch, {featureGroupId: featureGroupToDelete.id, licenseModelId}); + FeatureGroupsActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + FeatureGroupsActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js new file mode 100644 index 0000000000..e02c54595d --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js @@ -0,0 +1,60 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + FEATURE_GROUPS_LIST_LOADED: null, + ADD_FEATURE_GROUPS: null, + EDIT_FEATURE_GROUPS: null, + DELETE_FEATURE_GROUPS: null, + FEATURE_GROUPS_DELETE_CONFIRM: null, + + + ENTITLEMENT_POOLS_LIST_LOADED: null, + + featureGroupsEditor: { + OPEN: null, + CLOSE: null, + DATA_CHANGED: null, + SELECT_TAB: null, + SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: null, + SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB: null + } +}); + +export const state = keyMirror({ + SELECTED_FEATURE_GROUP_TAB: { + GENERAL: 1, + ENTITLEMENT_POOLS: 2, + LICENCE_KEY_GROUPS: 3 + }, + SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: { + ASSOCIATED_ENTITLEMENT_POOLS: 1, + AVAILABLE_ENTITLEMENT_POOLS: 2 + }, + SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB: { + ASSOCIATED_LICENSE_KEY_GROUPS: 1, + AVAILABLE_LICENSE_KEY_GROUPS: 2 + }, +}); + + + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js new file mode 100644 index 0000000000..576a5358e6 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './FeatureGroupsConstants.js'; + + + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.featureGroupsEditor.OPEN: + return { + ...state, + data: action.featureGroup || {} + }; + case actionTypes.featureGroupsEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + case actionTypes.featureGroupsEditor.CLOSE: + return {}; + case actionTypes.featureGroupsEditor.SELECT_TAB: + return { + ...state, + selectedTab: action.tab + }; + + case actionTypes.featureGroupsEditor.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: + return { + ...state, + selectedEntitlementPoolsButtonTab: action.buttonTab + }; + case actionTypes.featureGroupsEditor.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB: + return { + ...state, + selectedLicenseKeyGroupsButtonTab: action.buttonTab + }; + default: + return state; + } + +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js new file mode 100644 index 0000000000..5cf3248919 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './FeatureGroupsConstants.js'; +export default (state = [], action) => { + switch (action.type) { + case actionTypes.FEATURE_GROUPS_LIST_LOADED: + return [...action.response.results]; + case actionTypes.ADD_FEATURE_GROUPS: + return [...state, action.featureGroup]; + case actionTypes.EDIT_FEATURE_GROUPS: + const indexForEdit = state.findIndex(featureGroup => featureGroup.id === action.featureGroup.id); + return [...state.slice(0, indexForEdit), action.featureGroup, ...state.slice(indexForEdit + 1)]; + case actionTypes.DELETE_FEATURE_GROUPS: + return state.filter(featureGroup => featureGroup.id !== action.featureGroupId); + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js new file mode 100644 index 0000000000..9616b60b76 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js @@ -0,0 +1,160 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes as licenseAgreementActionTypes} from './LicenseAgreementConstants.js'; +import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; + +function baseUrl(licenseModelId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/license-agreements`; +} + +function fetchLicenseAgreementList(licenseModelId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`); +} + +function postLicenseAgreement(licenseModelId, licenseAgreement) { + return RestAPIUtil.create(baseUrl(licenseModelId), { + name: licenseAgreement.name, + description: licenseAgreement.description, + licenseTerm: licenseAgreement.licenseTerm, + requirementsAndConstrains: licenseAgreement.requirementsAndConstrains, + addedFeatureGroupsIds: licenseAgreement.featureGroupsIds + }); +} + +function putLicenseAgreement(licenseModelId, previousLicenseAgreement, licenseAgreement) { + const {featureGroupsIds = []} = licenseAgreement; + const {featureGroupsIds: prevFeatureGroupsIds = []} = previousLicenseAgreement; + return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${licenseAgreement.id}`, { + name: licenseAgreement.name, + description: licenseAgreement.description, + licenseTerm: licenseAgreement.licenseTerm, + requirementsAndConstrains: licenseAgreement.requirementsAndConstrains, + addedFeatureGroupsIds: featureGroupsIds.filter(featureGroupId => prevFeatureGroupsIds.indexOf(featureGroupId) === -1), + removedFeatureGroupsIds: prevFeatureGroupsIds.filter(prevFeatureGroupsId => featureGroupsIds.indexOf(prevFeatureGroupsId) === -1) + }); +} + +function deleteLicenseAgreement(licenseModelId, licenseAgreementId) { + return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${licenseAgreementId}`); +} + +export default { + + fetchLicenseAgreementList(dispatch, {licenseModelId, version}) { + return fetchLicenseAgreementList(licenseModelId, version).then(response => dispatch({ + type: licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED, + response + })); + }, + + openLicenseAgreementEditor(dispatch, {licenseModelId, licenseAgreement}) { + FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId}); + dispatch({ + type: licenseAgreementActionTypes.licenseAgreementEditor.OPEN, + licenseAgreement + }); + }, + + licenseAgreementEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: licenseAgreementActionTypes.licenseAgreementEditor.DATA_CHANGED, + deltaData + }); + }, + + closeLicenseAgreementEditor(dispatch) { + dispatch({ + type: licenseAgreementActionTypes.licenseAgreementEditor.CLOSE + }); + }, + + + saveLicenseAgreement(dispatch, {licenseModelId, previousLicenseAgreement, licenseAgreement}) { + if (previousLicenseAgreement) { + return putLicenseAgreement(licenseModelId, previousLicenseAgreement, licenseAgreement).then(() => { + dispatch({ + type: licenseAgreementActionTypes.EDIT_LICENSE_AGREEMENT, + licenseAgreement + }); + }); + } + else { + return postLicenseAgreement(licenseModelId, licenseAgreement).then(response => { + dispatch({ + type: licenseAgreementActionTypes.ADD_LICENSE_AGREEMENT, + licenseAgreement: { + ...licenseAgreement, + id: response.value + } + }); + }); + } + }, + + deleteLicenseAgreement(dispatch, {licenseModelId, licenseAgreementId}) { + return deleteLicenseAgreement(licenseModelId, licenseAgreementId).then(() => { + dispatch({ + type: licenseAgreementActionTypes.DELETE_LICENSE_AGREEMENT, + licenseAgreementId + }); + }); + }, + + selectLicenseAgreementEditorTab(dispatch, {tab}) { + dispatch({ + type: licenseAgreementActionTypes.licenseAgreementEditor.SELECT_TAB, + tab + }); + }, + + selectLicenseAgreementEditorFeatureGroupsButtonTab(dispatch, {buttonTab}) { + dispatch({ + type: licenseAgreementActionTypes.licenseAgreementEditor.SELECT_FEATURE_GROUPS_BUTTONTAB, + buttonTab + }); + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM, + licenseAgreementToDelete: false + }); + }, + + openDeleteLicenseAgreementConfirm(dispatch, {licenseAgreement} ) { + dispatch({ + type: licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM, + licenseAgreementToDelete: licenseAgreement + }); + }, + + switchVersion(dispatch, {licenseModelId, version}) { + LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => { + this.fetchLicenseAgreementList(dispatch, {licenseModelId, version}); + }); + } +}; + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx new file mode 100644 index 0000000000..42f2407696 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +function renderMsg(licenseAgreementToDelete) { + let name = licenseAgreementToDelete ? licenseAgreementToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{name}"?', {name}); + return( + <div> + <p>{msg}</p> + </div> + ); +}; + +const mapStateToProps = ({licenseModel: {licenseAgreement}}, {licenseModelId}) => { + let {licenseAgreementToDelete} = licenseAgreement; + const show = licenseAgreementToDelete !== false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: renderMsg(licenseAgreementToDelete), + confirmationDetails: {licenseAgreementToDelete, licenseModelId} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({licenseAgreementToDelete, licenseModelId}) => { + + LicenseAgreementActionHelper.deleteLicenseAgreement(dispatch, {licenseModelId, licenseAgreementId: licenseAgreementToDelete.id}); + LicenseAgreementActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + LicenseAgreementActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js new file mode 100644 index 0000000000..af5c454e22 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export const actionTypes = keyMirror({ + LICENSE_AGREEMENT_LIST_LOADED: null, + ADD_LICENSE_AGREEMENT: null, + EDIT_LICENSE_AGREEMENT: null, + DELETE_LICENSE_AGREEMENT: null, + LICENSE_AGREEMENT_DELETE_CONFIRM: null, + + licenseAgreementEditor: { + OPEN: null, + CLOSE: null, + DATA_CHANGED: null, + SELECT_TAB: null, + SELECT_FEATURE_GROUPS_BUTTONTAB: null, + } + +}); + +export const enums = keyMirror({ + SELECTED_LICENSE_AGREEMENT_TAB: { + GENERAL: 1, + FEATURE_GROUPS: 2 + }, + + SELECTED_FEATURE_GROUPS_BUTTONTAB: { + ASSOCIATED_FEATURE_GROUPS: 1, + AVAILABLE_FEATURE_GROUPS: 2 + } +}); + +export const defaultState = { + LICENSE_AGREEMENT_EDITOR_DATA: { + licenseTerm: {choice: '', other: ''} + } +}; + +export const optionsInputValues = { + LICENSE_MODEL_TYPE: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Fixed_Term', title: 'Fixed Term'}, + {enum: 'Perpetual', title: 'Perpetual'}, + {enum: 'Unlimited', title: 'Unlimited'} + ] +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js new file mode 100644 index 0000000000..6a3e4dbc73 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js @@ -0,0 +1,60 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js'; +import LicenseAgreementEditorView from './LicenseAgreementEditorView.jsx'; + +export const mapStateToProps = ({licenseModel: {licenseAgreement, featureGroup}}) => { + + + let {data, selectedTab, selectedFeatureGroupsButtonTab} = licenseAgreement.licenseAgreementEditor; + + let previousData; + const licenseAgreementId = data ? data.id : null; + if(licenseAgreementId) { + previousData = licenseAgreement.licenseAgreementList.find(licenseAgreement => licenseAgreement.id === licenseAgreementId); + } + + const {featureGroupsList = []} = featureGroup; + + return { + data, + previousData, + selectedTab, + selectedFeatureGroupsButtonTab, + featureGroupsList + }; +}; + +export const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onDataChanged: deltaData => LicenseAgreementActionHelper.licenseAgreementEditorDataChanged(dispatch, {deltaData}), + onTabSelect: tab => LicenseAgreementActionHelper.selectLicenseAgreementEditorTab(dispatch, {tab}), + onFeatureGroupsButtonTabSelect: buttonTab => LicenseAgreementActionHelper.selectLicenseAgreementEditorFeatureGroupsButtonTab(dispatch, {buttonTab}), + onCancel: () => LicenseAgreementActionHelper.closeLicenseAgreementEditor(dispatch), + onSubmit: ({previousLicenseAgreement, licenseAgreement}) => { + LicenseAgreementActionHelper.closeLicenseAgreementEditor(dispatch); + LicenseAgreementActionHelper.saveLicenseAgreement(dispatch, {licenseModelId, previousLicenseAgreement, licenseAgreement}); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(LicenseAgreementEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js new file mode 100644 index 0000000000..74e2f6e8c1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes, defaultState} from './LicenseAgreementConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.licenseAgreementEditor.OPEN: + return { + ...state, + data: action.licenseAgreement ? { ...action.licenseAgreement } : defaultState.LICENSE_AGREEMENT_EDITOR_DATA + }; + case actionTypes.licenseAgreementEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + case actionTypes.licenseAgreementEditor.CLOSE: + return {}; + case actionTypes.licenseAgreementEditor.SELECT_TAB: + return { + ...state, + selectedTab: action.tab + }; + case actionTypes.licenseAgreementEditor.SELECT_FEATURE_GROUPS_BUTTONTAB: + return { + ...state, + selectedFeatureGroupsButtonTab: action.buttonTab + }; + default: + return state; + } + +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx new file mode 100644 index 0000000000..b21f943fed --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx @@ -0,0 +1,247 @@ +import React from 'react'; +import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js'; +import Button from 'react-bootstrap/lib/Button.js'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationTabs from 'nfvo-components/input/validation/ValidationTabs.jsx'; +import ValidationTab from 'nfvo-components/input/validation/ValidationTab.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import DualListboxView from 'nfvo-components/input/dualListbox/DualListboxView.jsx'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorViewItem from 'nfvo-components/listEditor/ListEditorItemView.jsx'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import {enums as LicenseAgreementEnums, optionsInputValues as LicenseAgreementOptionsInputValues} from './LicenseAgreementConstants.js'; + + +const LicenseAgreementPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + requirementsAndConstrains: React.PropTypes.string, + licenseTerm: React.PropTypes.object, + featureGroupsIds: React.PropTypes.arrayOf(React.PropTypes.string) +}); + +class LicenseAgreementEditorView extends React.Component { + + static propTypes = { + data: LicenseAgreementPropType, + previousData: LicenseAgreementPropType, + isReadOnlyMode: React.PropTypes.bool, + onDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired, + + selectedTab: React.PropTypes.number, + onTabSelect: React.PropTypes.func, + + selectedFeatureGroupsButtonTab: React.PropTypes.number, + onFeatureGroupsButtonTabSelect: React.PropTypes.func, + featureGroupsList: DualListboxView.propTypes.availableList + }; + + static defaultProps = { + selectedTab: LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.GENERAL, + selectedFeatureGroupsButtonTab: LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS, + data: {} + }; + + state = { + localFeatureGroupsListFilter: '' + }; + + render() { + let {selectedTab, onTabSelect, isReadOnlyMode} = this.props; + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => this.props.onCancel() } + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + className='license-agreement-form'> + <ValidationTabs activeKey={onTabSelect ? selectedTab : undefined} onSelect={onTabSelect}> + {this.renderGeneralTab()} + {this.renderFeatureGroupsTab()} + </ValidationTabs> + </ValidationForm> + ); + } + + submit() { + const {data: licenseAgreement, previousData: previousLicenseAgreement} = this.props; + this.props.onSubmit({licenseAgreement, previousLicenseAgreement}); + } + + renderGeneralTab() { + let {data = {}, onDataChanged} = this.props; + let {name, description, requirementsAndConstrains, licenseTerm} = data; + return ( + <ValidationTab + eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.GENERAL} + title={i18n('General')}> + <div className='license-agreement-form-row'> + <div className='license-agreement-form-col'> + <ValidationInput + onChange={name => onDataChanged({name})} + label={i18n('Name')} + value={name} + name='license-agreement-name' + validations={{maxLength: 25, required: true}} + type='text'/> + <ValidationInput + onChange={requirementsAndConstrains => onDataChanged({requirementsAndConstrains})} + label={i18n('Requirements and Constraints')} + value={requirementsAndConstrains} + name='license-agreement-requirements-and-constraints' + validations={{maxLength: 1000}} + type='textarea'/> + </div> + <ValidationInput + onChange={description => onDataChanged({description})} + label={i18n('Description')} + value={description} + name='license-agreement-description' + validations={{maxLength: 1000, required: true}} + type='textarea'/> + </div> + <div className='license-agreement-form-row'> + <ValidationInput + onEnumChange={licenseTerm => onDataChanged({licenseTerm:{choice: licenseTerm, other: ''}})} + selectedEnum={licenseTerm && licenseTerm.choice} + validations={{required: true}} + type='select' + label={i18n('License Term')} + values={LicenseAgreementOptionsInputValues.LICENSE_MODEL_TYPE}/> + </div> + </ValidationTab> + ); + } + + renderFeatureGroupsTab() { + let {onFeatureGroupsButtonTabSelect, selectedFeatureGroupsButtonTab, featureGroupsList} = this.props; + if (featureGroupsList.length > 0) { + return ( + <ValidationTab + eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.FEATURE_GROUPS} + title={i18n('Feature Groups')}> + <ButtonGroup> + { + this.renderFeatureGroupsButtonTab( + LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.ASSOCIATED_FEATURE_GROUPS, + selectedFeatureGroupsButtonTab, + i18n('Associated Feature Groups'), + onFeatureGroupsButtonTabSelect + ) + } + { + this.renderFeatureGroupsButtonTab( + LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS, + selectedFeatureGroupsButtonTab, + i18n('Available Feature Groups'), + onFeatureGroupsButtonTabSelect + ) + } + </ButtonGroup> + {this.renderFeatureGroupsButtonTabContent(selectedFeatureGroupsButtonTab)} + </ValidationTab> + ); + } else { + return ( + <ValidationTab + eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.FEATURE_GROUPS} + title={i18n('Feature Groups')}> + <p>{i18n('There is no available feature groups')}</p> + </ValidationTab> + ); + } + } + + renderFeatureGroupsButtonTabContent(selectedFeatureGroupsButtonTab) { + const {featureGroupsList = [], data: {featureGroupsIds = []}} = this.props; + const {localFeatureGroupsListFilter} = this.state; + let selectedFeatureGroups = featureGroupsIds.map(featureGroupId => featureGroupsList.find(featureGroup => featureGroup.id === featureGroupId)); + + const dualBoxFilterTitle = { + left: i18n('Available Feature Groups'), + right: i18n('Selected Feature Groups') + }; + + switch (selectedFeatureGroupsButtonTab) { + case LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.ASSOCIATED_FEATURE_GROUPS: + if (!selectedFeatureGroups.length) { + return ( + <div className='no-items-msg'> + {i18n('There are currently no feature groups associated with this license agreement. Click "Available Feature Groups" to associate.')} + </div> + ); + } + if (featureGroupsList.length) { + return ( + <ListEditorView + className='thinner-list' + filterValue={localFeatureGroupsListFilter} + onFilter={localFeatureGroupsListFilter => this.setState({localFeatureGroupsListFilter})}> + {this.filterAssociatedFeatureGroupsList(selectedFeatureGroups).map(featureGroup => this.renderAssociatedFeatureGroupListItem(featureGroup))} + </ListEditorView> + ); + } + return; + case LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS: + return ( + <DualListboxView + filterTitle={dualBoxFilterTitle} + selectedValuesList={this.props.data.featureGroupsIds} + availableList={this.props.featureGroupsList} + onChange={ selectedValuesList => this.props.onDataChanged( { featureGroupsIds: selectedValuesList } )}/> + ); + } + } + + renderFeatureGroupsButtonTab(buttonTab, selectedButtonTab, title, onClick) { + const isSelected = buttonTab === selectedButtonTab; + return ( + <Button + className='button-tab' + active={isSelected} + onClick={() => !isSelected && onClick(buttonTab)}> + { title } + </Button> + ); + } + + renderAssociatedFeatureGroupListItem({id, name, entitlementPoolsIds = [], licenseKeyGroupsIds = []}) { + const {onDataChanged, data: {featureGroupsIds}, isReadOnlyMode} = this.props; + return ( + <ListEditorViewItem + key={id} + onDelete={() => onDataChanged({featureGroupsIds: featureGroupsIds.filter(featureGroupId => featureGroupId !== id)})} + isReadOnlyMode={isReadOnlyMode}> + <div className='name'>{name}</div> + <div className='inner-objects-count'>{ + i18n( + 'Entitlement Pools({entitlementPoolsCounter}), License Key Groups({licenseKeyGroupsCount})', + { + entitlementPoolsCounter: entitlementPoolsIds.length, + licenseKeyGroupsCount: licenseKeyGroupsIds.length + } + ) + }</div> + </ListEditorViewItem> + ); + } + + filterAssociatedFeatureGroupsList(featureGroupsList) { + let {localFeatureGroupsListFilter} = this.state; + if (localFeatureGroupsListFilter) { + const filter = new RegExp(escape(localFeatureGroupsListFilter), 'i'); + return featureGroupsList.filter(({name}) => name.match(filter)); + } + else { + return featureGroupsList; + } + } +} + +export default LicenseAgreementEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js new file mode 100644 index 0000000000..ca18bfab79 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; +import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js'; +import LicenseAgreementListEditorView from './LicenseAgreementListEditorView.jsx'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; + +const mapStateToProps = ({licenseModel: {licenseAgreement, licenseModelEditor}}) => { + let {licenseAgreementList} = licenseAgreement; + let {data} = licenseAgreement.licenseAgreementEditor; + let {vendorName} = licenseModelEditor.data; + + let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data); + + return { + vendorName, + licenseAgreementList, + isReadOnlyMode, + isDisplayModal: Boolean(data), + isModalInEditMode: Boolean(data && data.id) + }; +}; + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onAddLicenseAgreementClick: () => LicenseAgreementActionHelper.openLicenseAgreementEditor(dispatch, {licenseModelId}), + onEditLicenseAgreementClick: licenseAgreement => LicenseAgreementActionHelper.openLicenseAgreementEditor(dispatch, {licenseModelId, licenseAgreement}), + onDeleteLicenseAgreement: licenseAgreement => LicenseAgreementActionHelper.openDeleteLicenseAgreementConfirm(dispatch, {licenseAgreement}), + onCallVCAction: action => { + LicenseModelActionHelper.performVCAction(dispatch, {licenseModelId, action}).then(() => { + LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId}); + }); + }, + switchLicenseModelVersion: version => LicenseAgreementActionHelper.switchVersion(dispatch, {licenseModelId, version}), + onClose: () => OnboardingActionHelper.navigateToOnboardingCatalog(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(LicenseAgreementListEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx new file mode 100644 index 0000000000..4d7e704ba3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx @@ -0,0 +1,126 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; +import LicenseAgreementEditor from './LicenseAgreementEditor.js'; +import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx'; +import {optionsInputValues} from './LicenseAgreementConstants'; +import LicenseAgreementConfirmationModal from './LicenseAgreementConfirmationModal.jsx'; + + +class LicenseAgreementListEditorView extends React.Component { + static propTypes = { + vendorName: React.PropTypes.string, + licenseModelId: React.PropTypes.string.isRequired, + licenseAgreementList: React.PropTypes.array, + isReadOnlyMode: React.PropTypes.bool.isRequired, + isDisplayModal: React.PropTypes.bool, + isModalInEditMode: React.PropTypes.bool, + onAddLicenseAgreementClick: React.PropTypes.func, + onEditLicenseAgreementClick: React.PropTypes.func, + onDeleteLicenseAgreement: React.PropTypes.func, + onCallVCAction: React.PropTypes.func + }; + + static defaultProps = { + licenseAgreementList: [] + }; + + state = { + localFilter: '' + }; + + render() { + const {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props; + const {onAddLicenseAgreementClick} = this.props; + const {localFilter} = this.state; + + return ( + <div className='license-agreement-list-editor'> + <ListEditorView + title={i18n('License Agreements for {vendorName} License Model', {vendorName})} + plusButtonTitle={i18n('Add License Agreement')} + onAdd={onAddLicenseAgreementClick} + filterValue={localFilter} + onFilter={filter => this.setState({localFilter: filter})} + isReadOnlyMode={isReadOnlyMode}> + {this.filterList().map(licenseAgreement => this.renderLicenseAgreementListItem(licenseAgreement, isReadOnlyMode))} + </ListEditorView> + <Modal show={isDisplayModal} bsSize='large' animation={true} className='license-agreement-modal'> + <Modal.Header> + <Modal.Title>{`${isModalInEditMode ? i18n('Edit License Agreement') : i18n('Create New License Agreement')}`}</Modal.Title> + </Modal.Header> + <Modal.Body> + { + isDisplayModal && ( + <LicenseAgreementEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode} /> + ) + } + </Modal.Body> + </Modal> + <LicenseAgreementConfirmationModal licenseModelId={licenseModelId}/> + + </div> + ); + } + + filterList() { + let {licenseAgreementList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return licenseAgreementList.filter(({name = '', description = '', licenseTerm = ''}) => { + return escape(name).match(filter) || escape(description).match(filter) || escape(this.extractValue(licenseTerm)).match(filter); + }); + } + else { + return licenseAgreementList; + } + } + + renderLicenseAgreementListItem(licenseAgreement, isReadOnlyMode) { + let {id, name, description, licenseTerm, featureGroupsIds = []} = licenseAgreement; + let {onEditLicenseAgreementClick, onDeleteLicenseAgreement} = this.props; + return ( + <ListEditorItemView + key={id} + onSelect={() => onEditLicenseAgreementClick(licenseAgreement)} + onDelete={() => onDeleteLicenseAgreement(licenseAgreement)} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='text name'>{name}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='list-editor-item-view-field-tight'> + <div className='title'>{i18n('Type')}</div> + <div className='text type'>{this.extractValue(licenseTerm)}</div> + </div> + <div className='list-editor-item-view-field-tight'> + <div className='title'>{i18n('Feature')}</div> + <div className='title'>{i18n('Groups')}</div> + <div className='feature-groups-count'>{featureGroupsIds.length}</div> + </div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='text description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + extractValue(item) { + if (item === undefined) { + return ''; + } //TODO fix it later + + return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : ''; + } +} + +export default LicenseAgreementListEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js new file mode 100644 index 0000000000..5b5fa00df1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes as licenseAgreementActionTypes} from './LicenseAgreementConstants'; + +export default (state = [], action) => { + switch (action.type) { + case licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED: + return [...action.response.results]; + case licenseAgreementActionTypes.ADD_LICENSE_AGREEMENT: + return [...state, action.licenseAgreement]; + case licenseAgreementActionTypes.EDIT_LICENSE_AGREEMENT: + const indexForEdit = state.findIndex(licenseAgreement => licenseAgreement.id === action.licenseAgreement.id); + return [...state.slice(0, indexForEdit), action.licenseAgreement, ...state.slice(indexForEdit + 1)]; + case licenseAgreementActionTypes.DELETE_LICENSE_AGREEMENT: + return state.filter(licenseAgreement => licenseAgreement.id !== action.licenseAgreementId); + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js new file mode 100644 index 0000000000..50ac2c85a3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js @@ -0,0 +1,139 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes as licenseKeyGroupsConstants} from './LicenseKeyGroupsConstants.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; + +function baseUrl(licenseModelId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/license-key-groups`; +} + +function fetchLicenseKeyGroupsList(licenseModelId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`); +} + +function deleteLicenseKeyGroup(licenseModelId, licenseKeyGroupId) { + return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${licenseKeyGroupId}`); +} + +function postLicenseKeyGroup(licenseModelId, licenseKeyGroup) { + return RestAPIUtil.create(baseUrl(licenseModelId), { + name: licenseKeyGroup.name, + description: licenseKeyGroup.description, + operationalScope: licenseKeyGroup.operationalScope, + type: licenseKeyGroup.type + }); +} + +function putLicenseKeyGroup(licenseModelId, licenseKeyGroup) { + return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${licenseKeyGroup.id}`, { + name: licenseKeyGroup.name, + description: licenseKeyGroup.description, + operationalScope: licenseKeyGroup.operationalScope, + type: licenseKeyGroup.type + }); +} + + +export default { + fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version}) { + return fetchLicenseKeyGroupsList(licenseModelId, version).then(response => dispatch({ + type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_LIST_LOADED, + response + })); + }, + + openLicenseKeyGroupsEditor(dispatch, {licenseKeyGroup} = {}) { + dispatch({ + type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.OPEN, + licenseKeyGroup + }); + }, + + closeLicenseKeyGroupEditor(dispatch){ + dispatch({ + type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.CLOSE + }); + }, + + saveLicenseKeyGroup(dispatch, {licenseModelId, previousLicenseKeyGroup, licenseKeyGroup}) { + if (previousLicenseKeyGroup) { + return putLicenseKeyGroup(licenseModelId, licenseKeyGroup).then(() => { + dispatch({ + type: licenseKeyGroupsConstants.EDIT_LICENSE_KEY_GROUP, + licenseKeyGroup + }); + }); + } + else { + return postLicenseKeyGroup(licenseModelId, licenseKeyGroup).then(response => { + dispatch({ + type: licenseKeyGroupsConstants.ADD_LICENSE_KEY_GROUP, + licenseKeyGroup: { + ...licenseKeyGroup, + id: response.value + } + }); + }); + } + + + }, + + deleteLicenseKeyGroup(dispatch, {licenseModelId, licenseKeyGroupId}){ + return deleteLicenseKeyGroup(licenseModelId, licenseKeyGroupId).then(()=> { + dispatch({ + type: licenseKeyGroupsConstants.DELETE_LICENSE_KEY_GROUP, + licenseKeyGroupId + }); + }); + }, + + licenseKeyGroupEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.DATA_CHANGED, + deltaData + }); + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM, + licenseKeyGroupToDelete: false + }); + }, + + openDeleteLicenseAgreementConfirm(dispatch, {licenseKeyGroup}) { + dispatch({ + type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM, + licenseKeyGroupToDelete: licenseKeyGroup + }); + }, + + switchVersion(dispatch, {licenseModelId, version}) { + LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => { + this.fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version}); + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx new file mode 100644 index 0000000000..2413db51d0 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +function renderMsg(licenseKeyGroupToDelete) { + let name = licenseKeyGroupToDelete ? licenseKeyGroupToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{name}"?', {name}); + let subMsg = licenseKeyGroupToDelete + && licenseKeyGroupToDelete.referencingFeatureGroups + && licenseKeyGroupToDelete.referencingFeatureGroups.length > 0 ? + i18n('This license key group is associated with one or more feature groups') : + ''; + return( + <div> + <p>{msg}</p> + <p>{subMsg}</p> + </div> + ); +}; + +const mapStateToProps = ({licenseModel: {licenseKeyGroup}}, {licenseModelId}) => { + let {licenseKeyGroupToDelete} = licenseKeyGroup; + const show = licenseKeyGroupToDelete !== false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: renderMsg(licenseKeyGroupToDelete), + confirmationDetails: {licenseKeyGroupToDelete, licenseModelId} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({licenseKeyGroupToDelete, licenseModelId}) => { + + LicenseKeyGroupsActionHelper.deleteLicenseKeyGroup(dispatch, {licenseModelId, licenseKeyGroupId:licenseKeyGroupToDelete.id}); + LicenseKeyGroupsActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + LicenseKeyGroupsActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js new file mode 100644 index 0000000000..d32bc52744 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js @@ -0,0 +1,64 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export const actionTypes = keyMirror({ + + LICENSE_KEY_GROUPS_LIST_LOADED: null, + DELETE_LICENSE_KEY_GROUP: null, + EDIT_LICENSE_KEY_GROUP: null, + ADD_LICENSE_KEY_GROUP: null, + LICENSE_KEY_GROUPS_DELETE_CONFIRM: null, + licenseKeyGroupsEditor: { + OPEN: null, + CLOSE: null, + DATA_CHANGED: null, + } +}); + +export const defaultState = { + licenseKeyGroupsEditor: { + type: '', + operationalScope: {choices: [], other: ''} + } +}; + +export const optionsInputValues = { + OPERATIONAL_SCOPE: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Network_Wide', title: 'Network Wide'}, + {enum: 'Availability_Zone', title: 'Availability Zone'}, + {enum: 'Data_Center', title: 'Data Center'}, + {enum: 'Tenant', title: 'Tenant'}, + {enum: 'VM', title: 'VM'}, + {enum: 'CPU', title: 'CPU'}, + {enum: 'Core', title: 'Core'} + ], + TYPE: [ + {enum: '', title: i18n('please select…')}, + {enum: 'Universal', title: 'Universal'}, + {enum: 'Unique', title: 'Unique'}, + {enum: 'One_Time', title: 'One Time'} + ] +}; + + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js new file mode 100644 index 0000000000..3940ec594a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js'; +import LicenseKeyGroupsEditorView from './LicenseKeyGroupsEditorView.jsx'; + +const mapStateToProps = ({licenseModel: {licenseKeyGroup}}) => { + + + let {data} = licenseKeyGroup.licenseKeyGroupsEditor; + + let previousData; + const licenseKeyGroupId = data ? data.id : null; + if(licenseKeyGroupId) { + previousData = licenseKeyGroup.licenseKeyGroupsList.find(licenseKeyGroup => licenseKeyGroup.id === licenseKeyGroupId); + } + + return { + data, + previousData + }; +}; + +const mapActionsToProps = (dispatch, {licenseModelId}) => { + return { + onDataChanged: deltaData => LicenseKeyGroupsActionHelper.licenseKeyGroupEditorDataChanged(dispatch, {deltaData}), + onCancel: () => LicenseKeyGroupsActionHelper.closeLicenseKeyGroupEditor(dispatch), + onSubmit: ({previousLicenseKeyGroup, licenseKeyGroup}) => { + LicenseKeyGroupsActionHelper.closeLicenseKeyGroupEditor(dispatch); + LicenseKeyGroupsActionHelper.saveLicenseKeyGroup(dispatch, {licenseModelId, previousLicenseKeyGroup, licenseKeyGroup}); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(LicenseKeyGroupsEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js new file mode 100644 index 0000000000..a74498269a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes, defaultState} from './LicenseKeyGroupsConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.licenseKeyGroupsEditor.OPEN: + return { + ...state, + data: action.licenseKeyGroup ? {...action.licenseKeyGroup} : defaultState.licenseKeyGroupsEditor + }; + case actionTypes.licenseKeyGroupsEditor.CLOSE: + return {}; + case actionTypes.licenseKeyGroupsEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx new file mode 100644 index 0000000000..102e713060 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import {optionsInputValues as licenseKeyGroupOptionsInputValues} from './LicenseKeyGroupsConstants.js'; +import {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx'; + +const LicenseKeyGroupPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + operationalScope: React.PropTypes.shape({ + choices: React.PropTypes.array, + other: React.PropTypes.string + }), + type: React.PropTypes.string +}); + +class LicenseKeyGroupsEditorView extends React.Component { + static propTypes = { + data: LicenseKeyGroupPropType, + previousData: LicenseKeyGroupPropType, + isReadOnlyMode: React.PropTypes.bool, + onDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired + }; + + static defaultProps = { + data: {} + }; + + render() { + let {data = {}, onDataChanged, isReadOnlyMode} = this.props; + let {name, description, operationalScope, type} = data; + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => this.props.onCancel() } + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + className='license-key-groups-form'> + <div className='license-key-groups-form-row'> + <ValidationInput + onChange={name => onDataChanged({name})} + ref='name' + label={i18n('Name')} + value={name} + validations={{maxLength: 120, required: true}} + type='text'/> + <ValidationInput + isMultiSelect={true} + isRequired={true} + onEnumChange={operationalScope => onDataChanged({operationalScope:{choices: operationalScope, other: ''}})} + onOtherChange={operationalScope => onDataChanged({operationalScope:{choices: [optionInputOther.OTHER], other: operationalScope}})} + label={i18n('Operational Scope')} + validations={{required: true}} + multiSelectedEnum={operationalScope && operationalScope.choices} + otherValue={operationalScope && operationalScope.other} + values={licenseKeyGroupOptionsInputValues.OPERATIONAL_SCOPE}/> + </div> + <div className='license-key-groups-form-row'> + <ValidationInput + onChange={description => onDataChanged({description})} + ref='description' + label={i18n('Description')} + value={description} + validations={{maxLength: 1000, required: true}} + type='textarea'/> + <ValidationInput + isRequired={true} + onEnumChange={type => onDataChanged({type})} + selectedEnum={type} + label={i18n('Type')} + type='select' + validations={{required: true}} + values={licenseKeyGroupOptionsInputValues.TYPE}/> + </div> + </ValidationForm> + ); + } + + submit() { + const {data: licenseKeyGroup, previousData: previousLicenseKeyGroup} = this.props; + this.props.onSubmit({licenseKeyGroup, previousLicenseKeyGroup}); + } +} + +export default LicenseKeyGroupsEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js new file mode 100644 index 0000000000..e1b610f973 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js @@ -0,0 +1,50 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js'; +import LicenseKeyGroupsListEditorView from './LicenseKeyGroupsListEditorView.jsx'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +const mapStateToProps = ({licenseModel: {licenseKeyGroup, licenseModelEditor}}) => { + let {licenseKeyGroupsList} = licenseKeyGroup; + let {data} = licenseKeyGroup.licenseKeyGroupsEditor; + let {vendorName} = licenseModelEditor.data; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data); + + return { + vendorName, + licenseKeyGroupsList, + isReadOnlyMode, + isDisplayModal: Boolean(data), + isModalInEditMode: Boolean(data && data.id) + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onAddLicenseKeyGroupClick: () => LicenseKeyGroupsActionHelper.openLicenseKeyGroupsEditor(dispatch), + onEditLicenseKeyGroupClick: licenseKeyGroup => LicenseKeyGroupsActionHelper.openLicenseKeyGroupsEditor(dispatch, {licenseKeyGroup}), + onDeleteLicenseKeyGroupClick: licenseKeyGroup => LicenseKeyGroupsActionHelper.openDeleteLicenseAgreementConfirm(dispatch, {licenseKeyGroup}) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(LicenseKeyGroupsListEditorView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx new file mode 100644 index 0000000000..1ed1d2093a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx @@ -0,0 +1,138 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import LicenseKeyGroupsEditor from './LicenseKeyGroupsEditor.js'; +import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx'; +import {optionsInputValues} from './LicenseKeyGroupsConstants'; +import LicenseKeyGroupsConfirmationModal from './LicenseKeyGroupsConfirmationModal.jsx'; + + +class LicenseKeyGroupsListEditorView extends React.Component { + static propTypes = { + vendorName: React.PropTypes.string, + licenseModelId: React.PropTypes.string.isRequired, + licenseKeyGroupsList: React.PropTypes.array, + isReadOnlyMode: React.PropTypes.bool.isRequired, + isDisplayModal: React.PropTypes.bool, + isModalInEditMode: React.PropTypes.bool, + onAddLicenseKeyGroupClick: React.PropTypes.func, + onEditLicenseKeyGroupClick: React.PropTypes.func, + onDeleteLicenseKeyGroupClick: React.PropTypes.func + }; + + static defaultProps = { + licenseKeyGroupsList: [] + }; + + state = { + localFilter: '' + }; + + render() { + let {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props; + let {onAddLicenseKeyGroupClick} = this.props; + const {localFilter} = this.state; + + return ( + <div className='license-key-groups-list-editor'> + <ListEditorView + title={i18n('License Key Groups for {vendorName} License Model', {vendorName})} + plusButtonTitle={i18n('Add License Key Group')} + onAdd={onAddLicenseKeyGroupClick} + filterValue={localFilter} + onFilter={filter => this.setState({localFilter: filter})} + isReadOnlyMode={isReadOnlyMode}> + {this.filterList().map(licenseKeyGroup => (this.renderLicenseKeyGroupListItem(licenseKeyGroup, isReadOnlyMode)))} + </ListEditorView> + <Modal show={isDisplayModal} bsSize='large' animation={true} className='license-key-groups-modal'> + <Modal.Header> + <Modal.Title>{`${isModalInEditMode ? i18n('Edit License Key Group') : i18n('Create New License Key Group')}`}</Modal.Title> + </Modal.Header> + <Modal.Body> + { + isDisplayModal && ( + <LicenseKeyGroupsEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode}/> + ) + } + </Modal.Body> + </Modal> + <LicenseKeyGroupsConfirmationModal licenseModelId={licenseModelId}/> + + </div> + ); + } + + filterList() { + let {licenseKeyGroupsList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return licenseKeyGroupsList.filter(({name = '', description = '', operationalScope = '', type = ''}) => { + return escape(name).match(filter) || escape(description).match(filter) || escape(this.extractValue(operationalScope)).match(filter) || escape(type).match(filter); + }); + } + else { + return licenseKeyGroupsList; + } + } + + renderLicenseKeyGroupListItem(licenseKeyGroup, isReadOnlyMode) { + let {id, name, description, operationalScope, type} = licenseKeyGroup; + let {onEditLicenseKeyGroupClick, onDeleteLicenseKeyGroupClick} = this.props; + return ( + <ListEditorItemView + key={id} + onSelect={() => onEditLicenseKeyGroupClick(licenseKeyGroup)} + onDelete={() => onDeleteLicenseKeyGroupClick(licenseKeyGroup)} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='text name'>{name}</div> + </div> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Operational Scope')}</div> + <div className='text operational-scope'>{operationalScope && this.getOperationalScopes(operationalScope)}</div> + + <div className='title'>{i18n('Type')}</div> + <div className='text type'>{InputOptions.getTitleByName(optionsInputValues, type)}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='text description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + getOperationalScopes(operationalScope) { + if(operationalScope.choices.toString() === i18n(optionInputOther.OTHER) && operationalScope.other !== '') { + return operationalScope.other; + } + else { + let allOpScopes = ''; + for (let opScope of operationalScope.choices) { + allOpScopes += allOpScopes === '' ? InputOptions.getTitleByName(optionsInputValues, opScope) : `, ${InputOptions.getTitleByName(optionsInputValues, opScope)}`; + } + return allOpScopes; + } + } + + extractValue(item) { + if (item === undefined) { + return ''; + } //TODO fix it later + + return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : ''; + } +} + +export default LicenseKeyGroupsListEditorView; + + + diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js new file mode 100644 index 0000000000..54ce4e3955 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './LicenseKeyGroupsConstants.js'; +export default (state = [], action) => { + switch (action.type) { + case actionTypes.LICENSE_KEY_GROUPS_LIST_LOADED: + return [...action.response.results]; + case actionTypes.DELETE_LICENSE_KEY_GROUP: + return state.filter(licenseKeyGroup => licenseKeyGroup.id !== action.licenseKeyGroupId); + case actionTypes.ADD_LICENSE_KEY_GROUP: + return [...state, action.licenseKeyGroup]; + case actionTypes.EDIT_LICENSE_KEY_GROUP: + const indexForEdit = state.findIndex(licenseKeyGroup => licenseKeyGroup.id === action.licenseKeyGroup.id); + return [...state.slice(0, indexForEdit), action.licenseKeyGroup, ...state.slice(indexForEdit + 1)]; + default: + return state; + } +}; + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js new file mode 100644 index 0000000000..2dbef6baf2 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js @@ -0,0 +1,321 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import TabulatedEditor from 'src/nfvo-components/editor/TabulatedEditor.jsx'; + +import {enums} from 'sdc-app/onboarding/OnboardingConstants.js'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; + +import {navigationItems} from './SoftwareProductConstants.js'; +import SoftwareProductActionHelper from './SoftwareProductActionHelper.js'; +import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js'; + +const buildComponentNavigationBarGroups = ({componentId, meta}) => { + const groups = ([ + { + id: navigationItems.GENERAL + '|' + componentId, + name: i18n('General'), + disabled: false, + meta + }, { + id: navigationItems.COMPUTE + '|' + componentId, + name: i18n('Compute'), + disabled: false, + meta + }, { + id: navigationItems.LOAD_BALANCING + '|' + componentId, + name: i18n('High Availability & Load Balancing'), + disabled: false, + meta + }, { + id: navigationItems.NETWORKS + '|' + componentId, + name: i18n('Networks'), + disabled: false, + meta + }, { + id: navigationItems.STORAGE + '|' + componentId, + name: i18n('Storage'), + disabled: false, + meta + }, { + id: navigationItems.PROCESS_DETAILS + '|' + componentId, + name: i18n('Process Details'), + disabled: false, + meta + }, { + id: navigationItems.MONITORING + '|' + componentId, + name: i18n('Monitoring'), + disabled: false, + meta + } + ]); + + return groups; +}; + +const buildNavigationBarProps = ({softwareProduct, meta, screen, componentId, componentsList, mapOfExpandedIds}) => { + const {softwareProductEditor: {data: currentSoftwareProduct = {}}} = softwareProduct; + const {id, name} = currentSoftwareProduct; + const groups = [{ + id: id, + name: name, + items: [ + { + id: navigationItems.VENDOR_SOFTWARE_PRODUCT, + name: i18n('Overview'), + disabled: false, + meta + }, { + id: navigationItems.GENERAL, + name: i18n('General'), + disabled: false, + meta + }, { + id: navigationItems.PROCESS_DETAILS, + name: i18n('Process Details'), + disabled: false, + meta + }, { + id: navigationItems.NETWORKS, + name: i18n('Networks'), + disabled: false, + meta + }, { + id: navigationItems.ATTACHMENTS, + name: i18n('Attachments'), + disabled: false, + meta + }, { + id: navigationItems.COMPONENTS, + name: i18n('Components'), + hidden: componentsList.length <= 0, + meta, + expanded: mapOfExpandedIds[navigationItems.COMPONENTS] === true && screen !== enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, + items: [ + ...componentsList.map(({id, displayName}) => ({ + id: navigationItems.COMPONENTS + '|' + id, + name: displayName, + meta, + expanded: mapOfExpandedIds[navigationItems.COMPONENTS + '|' + id] === true && screen !== enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, + items: buildComponentNavigationBarGroups({componentId: id, meta}) + })) + ] + } + ] + }]; + let activeItemId = ({ + [enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE]: navigationItems.VENDOR_SOFTWARE_PRODUCT, + [enums.SCREEN.SOFTWARE_PRODUCT_DETAILS]: navigationItems.GENERAL, + [enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS]: navigationItems.ATTACHMENTS, + [enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES]: navigationItems.PROCESS_DETAILS, + [enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS]: navigationItems.NETWORKS, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS]: navigationItems.COMPONENTS + })[screen]; + + if(componentId) { + activeItemId = + Object.keys(mapOfExpandedIds).length === 1 && mapOfExpandedIds[navigationItems.COMPONENTS] === true ? + navigationItems.COMPONENTS : ({ + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL]: navigationItems.GENERAL, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE]: navigationItems.COMPUTE, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING]: navigationItems.LOAD_BALANCING, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK]: navigationItems.NETWORKS, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE]: navigationItems.STORAGE, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES]: navigationItems.PROCESS_DETAILS, + [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING]: navigationItems.MONITORING + })[screen] + '|' + componentId; + } + + return { + activeItemId, groups + }; +}; + +const buildVersionControllerProps = (softwareProduct) => { + const {softwareProductEditor} = softwareProduct; + const {data: currentSoftwareProduct = {}, isValidityData = true} = softwareProductEditor; + + const {version, viewableVersions, status: currentStatus, lockingUser} = currentSoftwareProduct; + const {status, isCheckedOut} = (currentStatus === versionStatusEnum.CHECK_OUT_STATUS) ? + VersionControllerUtils.getCheckOutStatusKindByUserID(currentStatus, lockingUser) : + {status: currentStatus, isCheckedOut: false}; + + return { + status, isCheckedOut, version, viewableVersions, + isFormDataValid: isValidityData + }; +}; + +const mapStateToProps = ({softwareProduct}, {currentScreen: {screen, props: {componentId}}}) => { + const {softwareProductEditor, softwareProductComponents, softwareProductQuestionnaire} = softwareProduct; + const {data: currentSoftwareProduct = {}, mapOfExpandedIds = []} = softwareProductEditor; + const {version} = currentSoftwareProduct; + const {componentsList = []} = softwareProductComponents; + const isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + const {qdata} = softwareProductQuestionnaire; + let currentComponentMeta = {}; + if(componentId) { + const {componentEditor: {data: componentData = {} , qdata: componentQdata}} = softwareProductComponents; + currentComponentMeta = {componentData, componentQdata}; + } + const meta = {softwareProduct: currentSoftwareProduct, qdata, version, isReadOnlyMode, currentComponentMeta}; + return { + versionControllerProps: buildVersionControllerProps(softwareProduct), + navigationBarProps: buildNavigationBarProps({softwareProduct, meta, screen, componentId, componentsList, mapOfExpandedIds}) + }; +}; + +const autoSaveBeforeNavigate = ({dispatch, screen, softwareProductId, componentId, meta: {isReadOnlyMode, softwareProduct, qdata, currentComponentMeta: {componentData, componentQdata}}}) => { + let promise; + if (isReadOnlyMode) { + promise = Promise.resolve(); + } else { + switch(screen) { + case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS: + promise = SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata}); + break; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL: + promise = SoftwareProductComponentsActionHelper.updateSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId: componentId, componentData, qdata: componentQdata}); + break; + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: + promise = SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata: componentQdata}); + break; + default: + promise = Promise.resolve(); + break; + } + } + return promise; +}; + + +const onComponentNavigate = (dispatch, {id, softwareProductId, version, currentComponentId}) => { + const [nextScreen, nextComponentId] = id.split('|'); + switch(nextScreen) { + case navigationItems.COMPONENTS: + if(nextComponentId === currentComponentId) { + OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId}); + } else { + OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId: nextComponentId, version}); + } + break; + case navigationItems.GENERAL: + OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId: nextComponentId, version}); + break; + case navigationItems.COMPUTE: + OnboardingActionHelper.navigateToComponentCompute(dispatch, {softwareProductId, componentId: nextComponentId}); + break; + case navigationItems.LOAD_BALANCING: + OnboardingActionHelper.navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId: nextComponentId}); + break; + case navigationItems.NETWORKS: + OnboardingActionHelper.navigateToComponentNetwork(dispatch, {softwareProductId, componentId: nextComponentId, version}); + break; + case navigationItems.STORAGE: + OnboardingActionHelper.navigateToComponentStorage(dispatch, {softwareProductId, componentId: nextComponentId}); + break; + case navigationItems.PROCESS_DETAILS: + OnboardingActionHelper.navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId: nextComponentId}); + break; + case navigationItems.MONITORING: + OnboardingActionHelper.navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId: nextComponentId}); + break; + } +}; + +const mapActionsToProps = (dispatch, {currentScreen: {screen, props: {softwareProductId, componentId: currentComponentId}}}) => { + + const props = { + onClose: ({version}) => { + if (screen === enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE) { + OnboardingActionHelper.navigateToOnboardingCatalog(dispatch); + } else { + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version}); + } + }, + onVersionSwitching: (version) => { + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version}); + }, + onToggle: (groups, itemIdToExpand) => groups.map(({items}) => SoftwareProductActionHelper.toggleNavigationItems(dispatch, {items, itemIdToExpand})), + onNavigate: ({id, meta}) => { + let preNavigate = autoSaveBeforeNavigate({dispatch, screen, meta, softwareProductId, componentId: currentComponentId}); + preNavigate.then(() => { + switch(id) { + case navigationItems.VENDOR_SOFTWARE_PRODUCT: + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version: meta.version}); + break; + case navigationItems.GENERAL: + OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, {softwareProductId}); + break; + case navigationItems.PROCESS_DETAILS: + OnboardingActionHelper.navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version: meta.version}); + break; + case navigationItems.NETWORKS: + OnboardingActionHelper.navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version: meta.version}); + break; + case navigationItems.ATTACHMENTS: + OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId}); + break; + case navigationItems.COMPONENTS: + OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId}); + break; + default: + onComponentNavigate(dispatch, {id, softwareProductId, version: meta.version, screen, currentComponentId}); + break; + } + }); + } + }; + + switch (screen) { + case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE: + case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS: + case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES: + case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING: + props.onSave = () => { + return Promise.resolve(); + }; + break; + default: + props.onSave = ({softwareProduct, qdata}) => SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata}); + break; + } + + + props.onVersionControllerAction = (action) => + SoftwareProductActionHelper.performVCAction(dispatch, {softwareProductId, action}).then(() => { + SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId}); + }); + + return props; +}; + +export default connect(mapStateToProps, mapActionsToProps)(TabulatedEditor); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js new file mode 100644 index 0000000000..d9ed8af679 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js @@ -0,0 +1,333 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js'; +import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js'; +import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js'; + +import {actionTypes} from './SoftwareProductConstants.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; +import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js'; +import {actionsEnum as VersionControllerActionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + +function baseUrl() { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/`; +} +function softwareProductCategoriesUrl() { + const restATTPrefix = Configuration.get('restATTPrefix'); + return `${restATTPrefix}/v1/categories/resources/`; +} + +function uploadFile(vspId, formData) { + + return RestAPIUtil.create(`${baseUrl()}${vspId}/upload`, formData); + +} + +function putSoftwareProduct(softwareData) { + return RestAPIUtil.save(`${baseUrl()}${softwareData.id}`, { + name: softwareData.name, + description: softwareData.description, + category: softwareData.category, + subCategory: softwareData.subCategory, + vendorId: softwareData.vendorId, + vendorName: softwareData.vendorName, + licensingVersion: softwareData.licensingVersion, + icon: softwareData.icon, + licensingData: softwareData.licensingData + }); +} + +function putSoftwareProductQuestionnaire(vspId, qdata) { + return RestAPIUtil.save(`${baseUrl()}${vspId}/questionnaire`, qdata); +} + +function putSoftwareProductAction(id, action) { + return RestAPIUtil.save(`${baseUrl()}${id}/actions`, {action: action}); +} + +function fetchSoftwareProductList() { + return RestAPIUtil.fetch(baseUrl()); +} + +function fetchSoftwareProduct(vspId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl()}${vspId}${versionQuery}`); +} + +function fetchSoftwareProductQuestionnaire(vspId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl()}${vspId}/questionnaire${versionQuery}`); +} + +function objToString(obj) { + let str = ''; + if (obj instanceof Array) { + obj.forEach((item) => { + str += objToString(item) + '\n'; + }); + } else { + for (let p in obj) { + if (obj.hasOwnProperty(p)) { + str += obj[p] + '\n'; + } + } + } + return str; +} + +function parseUploadErrorMsg(error) { + let message = ''; + for (let key in error) { + if (error.hasOwnProperty(key)) { + message += objToString(error[key]) + '\n'; + } + } + return message; +} + +function fetchSoftwareProductCategories(dispatch) { + let handleResponse = response => dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED, + softwareProductCategories: response + }); + return RestAPIUtil.fetch(softwareProductCategoriesUrl()) + .then(handleResponse) + .fail(() => handleResponse(null)); +} + +function loadLicensingData(dispatch, {licenseModelId, licensingVersion}) { + LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId, version: licensingVersion}); + FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId, version: licensingVersion}); +} + +function getExpandedItemsId(items, itemIdToToggle) { + for(let i = 0; i < items.length; i++) { + if(items[i].id === itemIdToToggle) { + if (items[i].expanded) { + return {}; + } else { + return {[itemIdToToggle]: true}; + } + } + else if(items[i].items && items[i].items.length > 0) { + let mapOfExpandedIds = getExpandedItemsId(items[i].items, itemIdToToggle); + if (mapOfExpandedIds !== false) { + mapOfExpandedIds[items[i].id] = true; + return mapOfExpandedIds; + } + } + } + return false; +} + +const SoftwareProductActionHelper = { + + loadSoftwareProductAssociatedData(dispatch) { + fetchSoftwareProductCategories(dispatch); + LicenseModelActionHelper.fetchFinalizedLicenseModels(dispatch); + }, + + loadSoftwareProductDetailsData(dispatch, {licenseModelId, licensingVersion}) { + SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch); + loadLicensingData(dispatch, {licenseModelId, licensingVersion}); + }, + + fetchSoftwareProductList(dispatch) { + return fetchSoftwareProductList().then(response => dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_LIST_LOADED, + response + })); + }, + + uploadFile(dispatch, {softwareProductId, formData, failedNotificationTitle}) { + Promise.resolve() + .then(() => uploadFile(softwareProductId, formData)) + .then(response => { + if (response.status !== 'Success') { + throw new Error(parseUploadErrorMsg(response.errors)); + } + }) + .then(() => { + SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(dispatch, {softwareProductId}); + OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId}); + SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId}); + }) + .catch(error => { + dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: {title: failedNotificationTitle, msg: error.message} + }); + }); + }, + + uploadConfirmation(dispatch, {softwareProductId, formData, failedNotificationTitle}) { + dispatch({ + type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION, + uploadData: { + softwareProductId, + formData, + failedNotificationTitle + } + }); + }, + hideUploadConfirm (dispatch) { + dispatch({ + type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION + }); + }, + updateSoftwareProduct(dispatch, {softwareProduct, qdata}) { + return Promise.all([ + SoftwareProductActionHelper.updateSoftwareProductData(dispatch, {softwareProduct}).then( + () => dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_LIST_EDIT, + payload: {softwareProduct} + }) + ), + SoftwareProductActionHelper.updateSoftwareProductQuestionnaire(dispatch, { + softwareProductId: softwareProduct.id, + qdata + }) + ]); + }, + + updateSoftwareProductData(dispatch, {softwareProduct}) { + return putSoftwareProduct(softwareProduct); + }, + + updateSoftwareProductQuestionnaire(dispatch, {softwareProductId, qdata}) { + return putSoftwareProductQuestionnaire(softwareProductId, qdata); + }, + + softwareProductEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.softwareProductEditor.DATA_CHANGED, + deltaData + }); + }, + + softwareProductQuestionnaireUpdate(dispatch, {data}) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE, + payload: {qdata: data} + }); + }, + + softwareProductEditorVendorChanged(dispatch, {deltaData}) { + LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId: deltaData.vendorId, version: deltaData.licensingVersion}); + FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId: deltaData.vendorId, version: deltaData.licensingVersion}); + SoftwareProductActionHelper.softwareProductEditorDataChanged(dispatch, {deltaData}); + }, + + setIsValidityData(dispatch, {isValidityData}) { + dispatch({ + type: actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED, + isValidityData + }); + }, + + addSoftwareProduct(dispatch, {softwareProduct}) { + dispatch({ + type: actionTypes.ADD_SOFTWARE_PRODUCT, + softwareProduct + }); + }, + + fetchSoftwareProduct(dispatch, {softwareProductId, version}) { + return Promise.all([ + fetchSoftwareProduct(softwareProductId, version).then(response => { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_LOADED, + response + }); + return response; + }), + fetchSoftwareProductQuestionnaire(softwareProductId, version).then(response => { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE, + payload: { + qdata: response.data ? JSON.parse(response.data) : {}, + qschema: JSON.parse(response.schema) + } + }); + }) + ]); + }, + + performVCAction(dispatch, {softwareProductId, action}) { + if (action === VersionControllerActionsEnum.SUBMIT) { + return putSoftwareProductAction(softwareProductId, action).then(() => { + return putSoftwareProductAction(softwareProductId, VersionControllerActionsEnum.CREATE_PACKAGE).then(() => { + dispatch({ + type: NotificationConstants.NOTIFY_SUCCESS, + data: { + title: i18n('Submit Succeeded'), + msg: i18n('This software product successfully submitted'), + timeout: 2000 + } + }); + fetchSoftwareProduct(softwareProductId).then(response => { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_LOADED, + response + }); + }); + }); + }, error => dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: {title: i18n('Submit Failed'), validationResponse: error.responseJSON} + })); + } + else { + return putSoftwareProductAction(softwareProductId, action).then(() => { + fetchSoftwareProduct(softwareProductId).then(response => { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_LOADED, + response + }); + }); + }); + } + }, + + switchVersion(dispatch, {softwareProductId, licenseModelId, version}) { + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, version}); + }, + + toggleNavigationItems(dispatch, {items, itemIdToExpand}) { + let mapOfExpandedIds = getExpandedItemsId(items, itemIdToExpand); + dispatch({ + type: actionTypes.TOGGLE_NAVIGATION_ITEM, + mapOfExpandedIds + }); + }, + + /** for the next verision */ + addComponent(dispatch) { + return dispatch; + } +}; + +export default SoftwareProductActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js new file mode 100644 index 0000000000..812afe5409 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +export default { + + getCurrentCategoryOfSubCategory(selectedSubCategory, softwareProductCategories) { + let category, subCategory; + for (var i = 0; i < softwareProductCategories.length; i++) { + let {subcategories = []} = softwareProductCategories[i]; + subCategory = subcategories.find(sub => sub.uniqueId === selectedSubCategory); + if (subCategory) { + category = softwareProductCategories[i].uniqueId; + break; + } + } + return category; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js new file mode 100644 index 0000000000..5f10c27084 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + SOFTWARE_PRODUCT_LOADED: null, + SOFTWARE_PRODUCT_LIST_LOADED: null, + SOFTWARE_PRODUCT_LIST_EDIT: null, + SOFTWARE_PRODUCT_CATEGORIES_LOADED: null, + SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE: null, + ADD_SOFTWARE_PRODUCT: null, + TOGGLE_NAVIGATION_ITEM: null, + + softwareProductEditor: { + OPEN: null, + CLOSE: null, + DATA_CHANGED: null, + IS_VALIDITY_DATA_CHANGED: null, + UPLOAD_CONFIRMATION: null + } +}); + +export const navigationItems = keyMirror({ + VENDOR_SOFTWARE_PRODUCT: 'Vendor Software Product', + GENERAL: 'General', + PROCESS_DETAILS: 'Process Details', + NETWORKS: 'Networks', + ATTACHMENTS: 'Attachments', + COMPONENTS: 'Components', + + COMPUTE: 'Compute', + LOAD_BALANCING: 'Load Balancing', + STORAGE: 'Storage', + MONITORING: 'Monitoring' +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js new file mode 100644 index 0000000000..6d1db1626f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.SOFTWARE_PRODUCT_LIST_LOADED: + return [...action.response.results]; + case actionTypes.SOFTWARE_PRODUCT_LIST_EDIT: + const indexForEdit = state.findIndex(vsp => vsp.id === action.payload.softwareProduct.id); + return [...state.slice(0, indexForEdit), action.payload.softwareProduct, ...state.slice(indexForEdit + 1)]; + case actionTypes.ADD_SOFTWARE_PRODUCT: + return [...state, action.softwareProduct]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js new file mode 100644 index 0000000000..784ac9db84 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js @@ -0,0 +1,80 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {combineReducers} from 'redux'; +import {actionTypes} from './SoftwareProductConstants.js'; +import SoftwareProductAttachmentsReducer from './attachments/SoftwareProductAttachmentsReducer.js'; +import SoftwareProductCreationReducer from './creation/SoftwareProductCreationReducer.js'; +import SoftwareProductDetailsReducer from './details/SoftwareProductDetailsReducer.js'; +import SoftwareProductProcessesListReducer from './processes/SoftwareProductProcessesListReducer.js'; +import SoftwareProductProcessesEditorReducer from './processes/SoftwareProductProcessesEditorReducer.js'; +import SoftwareProductNetworksListReducer from './networks/SoftwareProductNetworksListReducer.js'; +import SoftwareProductComponentsListReducer from './components/SoftwareProductComponentsListReducer.js'; +import SoftwareProductComponentEditorReducer from './components/SoftwareProductComponentEditorReducer.js'; +import {actionTypes as processesActionTypes} from './processes/SoftwareProductProcessesConstants.js'; +import SoftwareProductComponentProcessesListReducer from './components/processes/SoftwareProductComponentProcessesListReducer.js'; +import SoftwareProductComponentProcessesEditorReducer from './components/processes/SoftwareProductComponentProcessesEditorReducer.js'; +import {actionTypes as componentProcessesActionTypes} from './components/processes/SoftwareProductComponentProcessesConstants.js'; +import SoftwareProductComponentsNICListReducer from './components/network/SoftwareProductComponentsNICListReducer.js'; +import SoftwareProductComponentsNICEditorReducer from './components/network/SoftwareProductComponentsNICEditorReducer.js'; +import SoftwareProductComponentsMonitoringReducer from './components/monitoring/SoftwareProductComponentsMonitoringReducer.js'; + +export default combineReducers({ + softwareProductAttachments: SoftwareProductAttachmentsReducer, + softwareProductCreation: SoftwareProductCreationReducer, + softwareProductEditor: SoftwareProductDetailsReducer, + softwareProductProcesses: combineReducers({ + processesList: SoftwareProductProcessesListReducer, + processesEditor: SoftwareProductProcessesEditorReducer, + processToDelete: (state = false, action) => action.type === processesActionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM ? action.processToDelete : state + }), + softwareProductNetworks: combineReducers({ + networksList: SoftwareProductNetworksListReducer + }), + softwareProductComponents: combineReducers({ + componentsList: SoftwareProductComponentsListReducer, + componentEditor: SoftwareProductComponentEditorReducer, + componentProcesses: combineReducers({ + processesList: SoftwareProductComponentProcessesListReducer, + processesEditor: SoftwareProductComponentProcessesEditorReducer, + processToDelete: (state = false, action) => action.type === componentProcessesActionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM ? action.processToDelete : state, + }), + network: combineReducers({ + nicList: SoftwareProductComponentsNICListReducer, + nicEditor: SoftwareProductComponentsNICEditorReducer + }), + monitoring: SoftwareProductComponentsMonitoringReducer + }), + softwareProductCategories: (state = [], action) => { + if (action.type === actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED) { + return action.softwareProductCategories; + } + return state; + }, + softwareProductQuestionnaire: (state = {}, action) => { + if (action.type === actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE) { + return { + ...state, + ...action.payload + }; + } + return state; + } +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js new file mode 100644 index 0000000000..a4b95a4b7e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductAttachmentsView from './SoftwareProductAttachmentsView.jsx'; +import SoftwareProductAttachmentsActionHelper from './SoftwareProductAttachmentsActionHelper.js'; + +export const mapStateToProps = ({softwareProduct: {softwareProductAttachments}}) => { + let {attachmentsTree, hoveredNode, selectedNode, errorList} = softwareProductAttachments; + return { + attachmentsTree, + hoveredNode, + selectedNode, + errorList + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + toggleExpanded: (path) => SoftwareProductAttachmentsActionHelper.toggleExpanded(dispatch, {path}), + onSelectNode: (nodeName) => SoftwareProductAttachmentsActionHelper.onSelectNode(dispatch, {nodeName}), + onUnselectNode: () => SoftwareProductAttachmentsActionHelper.onUnselectNode(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductAttachmentsView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js new file mode 100644 index 0000000000..a7f7a5173b --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductAttachmentsConstants.js'; + +export default { + + toggleExpanded(dispatch, {path}) { + dispatch({ + type: actionTypes.TOGGLE_EXPANDED, + path + }); + }, + + onSelectNode(dispatch, {nodeName}) { + dispatch({ + type: actionTypes.SELECTED_NODE, + nodeName + }); + }, + + onUnselectNode(dispatch) { + dispatch({ + type: actionTypes.UNSELECTED_NODE + }); + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js new file mode 100644 index 0000000000..33af476d9c --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js @@ -0,0 +1,55 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export const actionTypes = keyMirror({ + TOGGLE_EXPANDED: null, + SELECTED_NODE: null, + UNSELECTED_NODE: null +}); + +export const errorTypes = keyMirror({ + MISSING_FILE_IN_ZIP: i18n('missing file in zip'), + MISSING_FILE_IN_MANIFEST: i18n('missing file in manifest'), + MISSING_OR_ILLEGAL_FILE_TYPE_IN_MANIFEST: i18n('missing or illegal file type in manifest'), + FILE_IS_YML_WITHOUT_YML_EXTENSION: i18n('file is defined as a heat file but it doesn\'t have .yml or .yaml extension'), + FILE_IS_ENV_WITHOUT_ENV_EXTENSION: i18n('file is defined as an env file but it doesn\'t have .env extension'), + ILLEGAL_YAML_FILE_CONTENT: i18n('illegal yaml file content'), + ILLEGAL_HEAT_YAML_FILE_CONTENT: i18n('illegal HEAT yaml file content'), + MISSING_FILE_NAME_IN_MANIFEST: i18n('a file is written in manifest without file name'), + MISSING_ENV_FILE_IN_ZIP: i18n('missing env file in zip'), + ARTIFACT_NOT_IN_USE: i18n('artifact not in use') +}); + +export const nodeTypes = keyMirror({ + heat: i18n('Heat'), + volume: i18n('Volume'), + network: i18n('Network'), + artifact: i18n('Artifact'), + env: i18n('Environment'), + other: i18n('') +}); + +export const mouseActions = keyMirror({ + MOUSE_BUTTON_CLICK: 0 +}); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js new file mode 100644 index 0000000000..5c5567b032 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js @@ -0,0 +1,199 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; +import {actionTypes} from './SoftwareProductAttachmentsConstants.js'; + +const mapVolumeData = ({fileName, env, errors}) => ({ + name: fileName, + expanded: true, + type: 'volume', + children: env && [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }], + errors +}); + +const mapNetworkData = ({fileName, env, errors}) => ({ + name: fileName, + expanded: true, + type: 'network', + children: env && [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }], + errors +}); + +const mapArtifactsData = ({fileName, errors}) => ({ + name: fileName, + type: 'artifact', + errors +}); + +const mapOtherData = ({fileName, errors}) => ({ + name: fileName, + type: 'other', + errors +}); + + +const mapHeatData = ({fileName, env, nested, volume, network, artifacts, errors, other}) => ({ + name: fileName, + expanded: true, + type: 'heat', + errors, + children: [ + ...(volume ? volume.map(mapVolumeData) : []), + ...(network ? network.map(mapNetworkData) : []), + ...(env ? [{ + name: env.fileName, + errors: env.errors, + type: 'env' + }] : []), + ...(artifacts ? artifacts.map(mapArtifactsData) : []), + ...(other ? other.map(mapOtherData) : []), + ...(nested ? nested.map(mapHeatData) : []) + ] +}); + +function createErrorList(node, parent, deep = 0, errorList = []) { + if (node.errors) { + errorList.push(...node.errors.map((error) => ({ + errorLevel: error.level, + errorMessage: error.message, + name: node.name, + hasParent: deep > 2, + parentName: parent.name, + type: node.type, + }))); + } + if (node.children && node.children.length) { + node.children.map((child) => createErrorList(child, node, deep + 1, errorList)); + } + return errorList; +} + +const mapValidationDataToTree = validationData => { + let {HEAT, volume, network, artifacts, other} = validationData.importStructure || {}; + return { + children: [ + { + name: 'HEAT', + expanded: true, + type: 'heat', + children: (HEAT ? HEAT.map(mapHeatData) : []) + }, + ...(artifacts ? [{ + name: 'artifacts', + expanded: true, + type: 'artifact', + children: (artifacts ? artifacts.map(mapArtifactsData) : []) + }] : []), + ...(network ? [{ + name: 'networks', + expanded: true, + type: 'network', + children: (network ? network.map(mapNetworkData) : []), + }] : []), + ...(volume ? [{ + name: 'volume', + expanded: true, + type: 'volume', + children: (volume ? volume.map(mapVolumeData) : []), + }] : []), + ...(other ? [{ + name: 'other', + expanded: true, + type: 'other', + children: (other ? other.map(mapOtherData) : []), + }] : []) + ] + }; +}; + +const toggleExpanded = (node, path) => { + let newNode = {...node}; + if (path.length === 0) { + newNode.expanded = !node.expanded; + } else { + let index = path[0]; + newNode.children = [ + ...node.children.slice(0, index), + toggleExpanded(node.children[index], path.slice(1)), + ...node.children.slice(index + 1) + ]; + } + return newNode; +}; + +const expandSelected = (node, selectedNode) => { + let shouldExpand = node.name === selectedNode; + let children = node.children && node.children.map(child => { + let {shouldExpand: shouldExpandChild, node: newChild} = expandSelected(child, selectedNode); + shouldExpand = shouldExpand || shouldExpandChild; + return newChild; + }); + + return { + node: { + ...node, + expanded: node.expanded || shouldExpand, + children + }, + shouldExpand + }; +}; + +export default (state = {attachmentsTree: {}}, action) => { + switch (action.type) { + case softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED: + let currentSoftwareProduct = action.response; + let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData) : {}; + let errorList = createErrorList(attachmentsTree); + return { + ...state, + attachmentsTree, + errorList + }; + case actionTypes.TOGGLE_EXPANDED: + return { + ...state, + attachmentsTree: toggleExpanded(state.attachmentsTree, action.path) + }; + case actionTypes.SELECTED_NODE: + let selectedNode = action.nodeName; + return { + ...state, + attachmentsTree: expandSelected(state.attachmentsTree, selectedNode).node, + selectedNode + }; + case actionTypes.UNSELECTED_NODE: + return { + ...state, + selectedNode: undefined + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx new file mode 100644 index 0000000000..c52999ca46 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx @@ -0,0 +1,182 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import classNames from 'classnames'; +import Collapse from 'react-bootstrap/lib/Collapse.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import {nodeTypes, mouseActions} from './SoftwareProductAttachmentsConstants'; + +const typeToIcon = Object.freeze({ + heat: 'building-o', + volume: 'database', + network: 'cloud', + artifact: 'gear', + env: 'server', + other: 'cube' +}); + +const leftPanelWidth = 250; + +class SoftwareProductAttachmentsView extends React.Component { + + static propTypes = { + attachmentsTree: React.PropTypes.object.isRequired + }; + state = { + treeWidth: '400' + }; + + render() { + let {attachmentsTree, errorList} = this.props; + let {treeWidth} = this.state; + return ( + <div className='software-product-attachments'> + <div className='software-product-attachments-tree' style={{'width' : treeWidth + 'px'}}> + <div className='tree-wrapper'> + { + attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind])) + } + </div> + </div> + <div onMouseDown={(e) => this.onChangeTreeWidth(e)} className='software-product-attachments-separator'/> + + <div className='software-product-attachments-error-list'> + {errorList.length ? this.renderErrorList(errorList) : <div className='no-errors'>{attachmentsTree.children ? + i18n('VALIDATION SUCCESS') : i18n('THERE IS NO HEAT DATA TO PRESENT') }</div>} + </div> + </div> + ); + } + + renderNode(node, path) { + let isFolder = node.children && node.children.length > 0; + let {onSelectNode} = this.props; + return ( + <div key={node.name} className='tree-block-inside'> + { + <div onDoubleClick={() => this.props.toggleExpanded(path)} className={this.getTreeRowClassName(node.name)}> + { + isFolder && + <div onClick={() => this.props.toggleExpanded(path)} className={classNames('tree-node-expander', {'tree-node-expander-collapsed': !node.expanded})}> + <FontAwesome name='caret-down'/> + </div> + } + { + + <span className='tree-node-icon'> + <FontAwesome name={typeToIcon[node.type]}/> + </span> + } + { + + <span onClick={() => onSelectNode(node.name)} className={this.getTreeTextClassName(node)}> + {node.name} + </span> + } + </div> + } + { + isFolder && + <Collapse in={node.expanded}> + <div className='tree-node-children'> + { + node.children.map((child, ind) => this.renderNode(child, [...path, ind])) + } + </div> + </Collapse> + } + </div> + ); + } + + createErrorList(errorList, node, parent) { + if (node.errors) { + node.errors.forEach(error => errorList.push({ + error, + name: node.name, + parentName: parent.name, + type: node.type + })); + } + if (node.children && node.children.length) { + node.children.map((child) => this.createErrorList(errorList, child, node)); + } + } + + renderErrorList(errors) { + let prevError = {}; + let {selectedNode} = this.props; + return errors.map(error => { + let isSameNodeError = error.name === prevError.name && error.parentName === prevError.parentName; + prevError = error; + + return ( + <div + key={error.name + error.errorMessage + error.parentName} + + onClick={() => this.selectNode(error.name)} + className={classNames('error-item', {'clicked': selectedNode === error.name, 'shifted': !isSameNodeError})}> + <span className={classNames('error-item-file-type', {'strong': !isSameNodeError})}> + { + error.hasParent ? + i18n('{type} {name} in {parentName}: ', { + type: nodeTypes[error.type], + name: error.name, + parentName: error.parentName + }) : + i18n('{type} {name}: ', { + type: nodeTypes[error.type], + name: error.name + }) + } + </span> + <span className={`error-item-file-type ${error.errorLevel}`}> {error.errorMessage} </span> + </div> + ); + }); + } + + selectNode(currentSelectedNode) { + let {onUnselectNode, onSelectNode, selectedNode} = this.props; + if (currentSelectedNode !== selectedNode) { + onSelectNode(currentSelectedNode); + }else{ + onUnselectNode(); + } + + } + + getTreeRowClassName(name) { + let {hoveredNode, selectedNode} = this.props; + return classNames({ + 'tree-node-row': true, + 'tree-node-selected': name === hoveredNode, + 'tree-node-clicked': name === selectedNode + }); + } + + getTreeTextClassName(node) { + let {selectedNode} = this.props; + return classNames({ + 'tree-element-text': true, + 'error-status': node.errors, + 'error-status-selected': node.name === selectedNode + }); + } + + onChangeTreeWidth(e) { + if (e.button === mouseActions.MOUSE_BUTTON_CLICK) { + let onMouseMove = (e) => { + this.setState({treeWidth: e.clientX - leftPanelWidth}); + }; + let onMouseUp = () => { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + } +} + +export default SoftwareProductAttachmentsView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js new file mode 100644 index 0000000000..3b8bc4f171 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentsConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.COMPONENT_UPDATE: + return { + ...state, + data: action.component + }; + case actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE: + return { + ...state, + qdata: action.payload.qdata || state.qdata, + qschema: action.payload.qschema || state.qschema + }; + case actionTypes.COMPONENT_DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js new file mode 100644 index 0000000000..e53b2ecafe --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js @@ -0,0 +1,129 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +import {actionTypes} from './SoftwareProductComponentsConstants.js'; + +function baseUrl(softwareProductId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components`; +} + +function fetchSoftwareProductComponents(softwareProductId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`); +} + +function putSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, vspComponent) { + return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${vspComponentId}/questionnaire`, vspComponent); +} + +function fetchSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, version){ + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}/${vspComponentId}/questionnaire${versionQuery}`); +} + +function fetchSoftwareProductComponent(softwareProductId, vspComponentId, version){ + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}/${vspComponentId}${versionQuery}`); +} + +function putSoftwareProductComponent(softwareProductId, vspComponentId, vspComponent) { + return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${vspComponentId}`, { + name: vspComponent.name, + displayName: vspComponent.displayName, + description: vspComponent.description + }); +} + +const SoftwareProductComponentsActionHelper = { + fetchSoftwareProductComponents(dispatch, {softwareProductId, version}) { + return fetchSoftwareProductComponents(softwareProductId, version).then(response => { + dispatch({ + type: actionTypes.COMPONENTS_LIST_UPDATE, + componentsList: response.results + }); + }); + }, + + componentDataChanged(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.COMPONENT_DATA_CHANGED, + deltaData + }); + }, + + + updateSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId, componentData, qdata}) { + return Promise.all([ + SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, qdata}), + SoftwareProductComponentsActionHelper.updateSoftwareProductComponentData(dispatch, {softwareProductId, vspComponentId, componentData}) + ]); + }, + + updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, qdata}) { + return putSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, qdata); + }, + + updateSoftwareProductComponentData(dispatch, {softwareProductId, vspComponentId, componentData}) { + return putSoftwareProductComponent(softwareProductId, vspComponentId, componentData).then(() => dispatch({ + type: actionTypes.COMPONENTS_LIST_EDIT, + component: { + id: vspComponentId, + ...componentData + } + })); + }, + + + fetchSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, version}) { + return fetchSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, version).then(response => { + dispatch({ + type: actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE, + payload: { + qdata: response.data ? JSON.parse(response.data) : {}, + qschema: JSON.parse(response.schema) + } + }); + }); + }, + + fetchSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId, version}) { + return fetchSoftwareProductComponent(softwareProductId, vspComponentId, version).then(response => { + dispatch({ + type: actionTypes.COMPONENT_UPDATE, + component: response.data + }); + }); + }, + + componentQuestionnaireUpdated(dispatch, {data}) { + dispatch({ + type: actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE, + payload: { + qdata: data + } + }); + }, +}; + +export default SoftwareProductComponentsActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js new file mode 100644 index 0000000000..dee517e5d1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js @@ -0,0 +1,46 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + COMPONENTS_LIST_UPDATE: null, + COMPONENTS_LIST_EDIT: null, + COMPONENT_UPDATE: null, + COMPONENT_DATA_CHANGED: null, + COMPONENT_QUESTIONNAIRE_UPDATE: null +}); + +export const storageConstants = keyMirror({ + backupType: { + ON_SITE: 'OnSite', + OFF_SITE: 'OffSite' + } +}); + +export const navigationItems = keyMirror({ + STORAGE: 'Storage', + PROCESS_DETAILS: 'Process Details', + MONITORING: 'Monitoring', + NETWORK: 'Network', + COMPUTE: 'Compute', + NETWORK: 'Network', + LOAD_BALANCING: 'High Availability & Load Balancing' +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js new file mode 100644 index 0000000000..f1c1ed1fcc --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js @@ -0,0 +1,48 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentsListView from './SoftwareProductComponentsListView.jsx'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentSoftwareProduct}, softwareProductComponents} = softwareProduct; + let {componentsList} = softwareProductComponents; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + + return { + currentSoftwareProduct, + isReadOnlyMode, + componentsList + }; +}; + + +const mapActionToProps = (dispatch, {version}) => { + return { + onComponentSelect: ({id: softwareProductId, componentId}) => { + OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version }); + } + }; +}; + +export default connect(mapStateToProps, mapActionToProps, null, {withRef: true})(SoftwareProductComponentsListView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js new file mode 100644 index 0000000000..b91362a0cf --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentsConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.COMPONENTS_LIST_UPDATE: + return [...action.componentsList]; + case actionTypes.COMPONENTS_LIST_EDIT: + const indexForEdit = state.findIndex(component => component.id === action.component.id); + return [...state.slice(0, indexForEdit), action.component, ...state.slice(indexForEdit + 1)]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx new file mode 100644 index 0000000000..0c0ba0f646 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx @@ -0,0 +1,89 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +const ComponentPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + displayName: React.PropTypes.string, + description: React.PropTypes.string +}); + +class SoftwareProductComponentsListView extends React.Component { + + state = { + localFilter: '' + }; + + static propTypes = { + isReadOnlyMode: React.PropTypes.bool, + componentsList: React.PropTypes.arrayOf(ComponentPropType), + onComponentSelect: React.PropTypes.func + }; + + render() { + let {componentsList = []} = this.props; + return ( + <div className=''> + { + componentsList.length > 0 && this.renderComponents() + } + </div> + ); + } + + renderComponents() { + const {localFilter} = this.state; + let {isReadOnlyMode} = this.props; + + return ( + <ListEditorView + title={i18n('Virtual Function Components')} + filterValue={localFilter} + placeholder={i18n('Filter Components')} + onFilter={filter => this.setState({localFilter: filter})} + isReadOnlyMode={isReadOnlyMode}> + {this.filterList().map(component => this.renderComponentsListItem(component))} + </ListEditorView> + ); + } + + renderComponentsListItem(component) { + let {id: componentId, name, displayName, description = ''} = component; + let {currentSoftwareProduct: {id}, onComponentSelect} = this.props; + return ( + <ListEditorItemView + key={name + Math.floor(Math.random() * (100 - 1) + 1).toString()} + className='list-editor-item-view' + onSelect={() => onComponentSelect({id, componentId})}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Component')}</div> + <div className='name'>{displayName}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + filterList() { + let {componentsList = []} = this.props; + + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return componentsList.filter(({displayName = '', description = ''}) => { + return escape(displayName).match(filter) || escape(description).match(filter); + }); + } + else { + return componentsList; + } + } +} + +export default SoftwareProductComponentsListView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js new file mode 100644 index 0000000000..7ac1c707ab --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentComputeView from './SoftwareProductComponentComputeView.jsx'; +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct; + let {componentEditor: {qdata, qschema}} = softwareProductComponents; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP); + + let minNumberOfVMsSelectedByUser = 0; + if(qdata && qdata.compute && qdata.compute.numOfVMs){ + minNumberOfVMsSelectedByUser = qdata.compute.numOfVMs.minimum || 0; + } + + return { + qdata, + qschema, + isReadOnlyMode, + minNumberOfVMsSelectedByUser + }; +}; + +const mapActionToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}), + onSubmit: ({qdata}) =>{ return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});} + }; +}; + +export default connect(mapStateToProps, mapActionToProps, null, {withRef: true}) (SoftwareProductComponentComputeView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx new file mode 100644 index 0000000000..3bad147117 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx @@ -0,0 +1,129 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx'; + + +class SoftwareProductComponentComputeView extends React.Component { + + static propTypes = { + qdata: React.PropTypes.object, + qschema: React.PropTypes.object, + isReadOnlyMode: React.PropTypes.bool, + minNumberOfVMsSelectedByUser: React.PropTypes.number, + onQDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired + }; + + render() { + let {qdata, qschema, isReadOnlyMode, minNumberOfVMsSelectedByUser, onQDataChanged, onSubmit} = this.props; + + return ( + <div className='vsp-component-questionnaire-view'> + <ValidationForm + ref='computeValidationForm' + hasButtons={false} + onSubmit={() => onSubmit({qdata})} + className='component-questionnaire-validation-form' + isReadOnlyMode={isReadOnlyMode} + onDataChanged={onQDataChanged} + data={qdata} + schema={qschema}> + + <div className='section-title'>{i18n('VM Sizing')}</div> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Number of CPUs')} + pointer={'/compute/vmSizing/numOfCPUs'}/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('File System Size (GB)')} + pointer={'/compute/vmSizing/fileSystemSizeGB'}/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Persistent Storage/Volume Size (GB)')} + pointer={'/compute/vmSizing/persistentStorageVolumeSize'}/> + </div> + <ValidationInput + type='text' + label={i18n('I/O Operations (per second)')} + pointer={'/compute/vmSizing/IOOperationsPerSec'}/> + </div> + </div> + <div className='section-title'>{i18n('Number of VMs')}</div> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Minimum')} + pointer={'/compute/numOfVMs/minimum'}/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Maximum')} + pointer={'/compute/numOfVMs/maximum'} + validations={{minValue: minNumberOfVMsSelectedByUser}}/> + </div> + <div className='single-col'> + <ValidationInput + type='select' + label={i18n('CPU Oversubscription Ratio')} + pointer={'/compute/numOfVMs/CpuOverSubscriptionRatio'}/> + </div> + <ValidationInput + type='select' + label={i18n('Memory - RAM')} + pointer={'/compute/numOfVMs/MemoryRAM'}/> + </div> + </div> + + <div className='section-title'>{i18n('Guest OS')}</div> + <div className='rows-section'> + <div className='section-field row-flex-components input-row'> + <div className='two-col'> + <ValidationInput + label={i18n('Guest OS')} + type='text' + pointer={'/compute/guestOS/name'}/> + </div> + <div className='empty-two-col'/> + </div> + <div className='vertical-flex input-row'> + <label key='label' className='control-label'>{i18n('OS Bit Size')}</label> + <div className='radio-options-content-row input-row'> + <ValidationInput + type='radiogroup' + pointer={'/compute/guestOS/bitSize'} + className='radio-field'/> + </div> + </div> + <div className='section-field row-flex-components input-row'> + <div className='two-col'> + <ValidationInput + type='textarea' + label={i18n('Guest OS Tools:')} + pointer={'/compute/guestOS/tools'}/> + </div> + <div className='empty-two-col'/> + </div> + </div> + </ValidationForm> + </div> + ); + } + + save(){ + return this.refs.computeValidationForm.handleFormSubmit(new Event('dummy')); + } +} + +export default SoftwareProductComponentComputeView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js new file mode 100644 index 0000000000..e4c330bec8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentsGeneralView from './SoftwareProductComponentsGeneralView.jsx'; +import SoftwareProductComponentsActionHelper from '../SoftwareProductComponentsActionHelper.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +export const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct; + let {componentEditor: {data: componentData = {} , qdata, qschema}} = softwareProductComponents; + + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP); + + return { + componentData, + qdata, + qschema, + isReadOnlyMode + }; +}; + + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onDataChanged: deltaData => SoftwareProductComponentsActionHelper.componentDataChanged(dispatch, {deltaData}), + onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}), + onSubmit: ({componentData, qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponent(dispatch, + {softwareProductId, vspComponentId: componentId, componentData, qdata}); + } + }; + +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsGeneralView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx new file mode 100644 index 0000000000..5d11e42cd3 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx @@ -0,0 +1,186 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx'; + + +class SoftwareProductComponentsGeneralView extends React.Component { + + render() { + let {qdata, qschema, onQDataChanged, onDataChanged, componentData: {displayName, description}, isReadOnlyMode} = this.props; + return( + <div className='vsp-components-general'> + <div className='general-data'> + <ValidationForm + isReadOnlyMode={isReadOnlyMode} + hasButtons={false}> + <div className=''> + <h3 className='section-title'>{i18n('General')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + {/** disabled until backend will be ready to implement it + <div className='validation-input-wrapper'> + <div className='form-group'> + <label className='control-label'>{i18n('Name')}</label> + <div>{name}</div> + </div> + </div> + + */} + <div className='single-col'> + <ValidationInput label={i18n('Name')} value={displayName} disabled={true} type='text'/> + </div> + <div className='two-col'> + <ValidationInput + label={i18n('Description')} + onChange={description => onDataChanged({description})} + disabled={isReadOnlyMode} + value={description} + type='textarea'/> + </div> + <div className='empty-col' /> + </div> + </div> + </div> + </ValidationForm> + { + qschema && + <ValidationForm + onDataChanged={onQDataChanged} + data={qdata} + schema={qschema} + isReadOnlyMode={isReadOnlyMode} + hasButtons={false}> + <h3 className='section-title additional-validation-form'>{i18n('Hypervisor')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + label={i18n('Supported Hypervisors')} + type='select' + pointer='/general/hypervisor/hypervisor'/> + </div> + <div className='two-col'> + <ValidationInput + label={i18n('Hypervisor Drivers')} + type='text' + pointer='/general/hypervisor/drivers'/> + </div> + <div className='empty-col' /> + </div> + <div className='row-flex-components input-row'> + <div className='three-col'> + <ValidationInput + label={i18n('Describe Container Features')} + type='textarea' + pointer='/general/hypervisor/containerFeaturesDescription'/> + </div> + <div className='empty-col' /> + </div> + </div> + <h3 className='section-title'>{i18n('Image')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + label={i18n('Image format')} + type='select' + pointer='/general/image/format'/> + </div> + <div className='single-col'> + <ValidationInput + label={i18n('Image provided by')} + type='select' + pointer='/general/image/providedBy'/> + </div> + <div className='single-col'> + <ValidationInput + label={i18n('Size of boot disk per VM (GB)')} + type='text' + pointer='/general/image/bootDiskSizePerVM'/> + </div> + <ValidationInput + label={i18n('Size of ephemeral disk per VM (GB)')} + type='text' + pointer='/general/image/ephemeralDiskSizePerVM'/> + </div> + </div> + <h3 className='section-title'>{i18n('Recovery')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + label={i18n('VM Recovery Point Objective (Minutes)')} + type='text' + pointer='/general/recovery/pointObjective'/> + </div> + <ValidationInput + label={i18n('VM Recovery Time Objective (Minutes)')} + type='text' + pointer='/general/recovery/timeObjective'/> + <div className='empty-two-col' /> + </div> + + + <div className='row-flex-components input-row'> + <div className='two-col'> + <ValidationInput + className='textarea' + label={i18n('How are in VM process failures handled?')} + type='textarea' + pointer='/general/recovery/vmProcessFailuresHandling'/> + </div> + <div className='empty-two-col' /> + { + /** disabled until backend will be ready to implement it + <div className='row'> + <div className='col-md-3'> + <ValidationInput + label={i18n('VM Recovery Document')} + type='text' + pointer='/general/recovery/VMRecoveryDocument'/> + </div> + </div> + */ + } + </div> + </div> + <h3 className='section-title'>{i18n('DNS Configuration')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='two-col'> + <ValidationInput + label={i18n('Do you have a need for DNS as a Service? Please describe.')} + type='textarea' + pointer='/general/dnsConfiguration'/> + </div> + <div className='empty-two-col' /> + </div> + </div> + <h3 className='section-title'>{i18n('Clone')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='two-col'> + <ValidationInput + label={i18n('Describe VM Clone Use')} + type='textarea' + pointer='/general/vmCloneUsage'/> + </div> + <div className='empty-two-col' /> + </div> + </div> + </ValidationForm> + } + </div> + </div> + ); + } + + save() { + let {onSubmit, componentData, qdata} = this.props; + return onSubmit({componentData, qdata}); + } +} + +export default SoftwareProductComponentsGeneralView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js new file mode 100644 index 0000000000..4d4034de5b --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentLoadBalancingView from './SoftwareProductComponentLoadBalancingRefView.jsx'; +import SoftwareProductComponentsActionHelper from '../SoftwareProductComponentsActionHelper.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +export const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct; + let {componentEditor: {qdata, qschema}} = softwareProductComponents; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP); + + return { + qdata, + qschema, + isReadOnlyMode + }; +}; + + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}), + onSubmit: ({qdata}) =>{ return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});} + }; + +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentLoadBalancingView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx new file mode 100644 index 0000000000..1aa2babc12 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx'; + +const prefix = '/highAvailabilityAndLoadBalancing/'; + +const pointers = [ + { + key: 'failureLoadDistribution', + description: 'How is load distributed across live vms in the event of a vm/host failure? please describe' + }, + { + key: 'nkModelImplementation', + description: 'Does each VM implement the N+K model for redundancy and failure protection? Please describe.' + }, + { + key: 'architectureChoice', + description: 'What architecture is being implemented: ACTIVE-ACTIVE and/or ACTIVE-PASSIVE. ', + added: 'Will the arrangement be 1-1 or N-M? Please describe.' + }, + {key: 'slaRequirements', description: 'Specify application SLA requirements on Cloud platform.'}, + { + key: 'horizontalScaling', + description: 'Is horizontal scaling the preferred solution for HA and resiliency? Please describe.' + }, + { + key: 'loadDistributionMechanism', + description: 'Can load be distributed across VMs? If so, are special mechanisms needed to re-balance data across VMs?', + added: 'Please describe.' + } +]; + +class SoftwareProductComponentLoadBalancingView extends React.Component { + static propTypes = { + componentId: React.PropTypes.string.isRequired, + softwareProductId: React.PropTypes.string.isRequired, + qdata: React.PropTypes.object, + qschema: React.PropTypes.object, + currentSoftwareProduct: React.PropTypes.object + }; + + state = { + expanded: {} + }; + + renderTextAreaItem(item) { + return ( + <div className='question'> + <div className={this.state.expanded[item.key] ? 'title' : 'title add-padding'} + onClick={() => this.toggle(item.key)}> + <FontAwesome name={this.state.expanded[item.key] ? 'chevron-up' : 'chevron-down'}/> + {i18n(item.description)} + {item.added && <div className='new-line'>{i18n(item.added)}</div>} + </div> + <div className={this.state.expanded[item.key] ? 'collapse in' : 'collapse'}> + <div className='row'> + <div className='col-md-9'> + <ValidationInput + type='textarea' + pointer={`${prefix}${item.key}`} + maxLength='1000' /> + </div> + </div> + </div> + </div> + ); + } + + render() { + let {qdata, qschema, onQDataChanged, isReadOnlyMode} = this.props; + return ( + <div className='vsp-components-load-balancing'> + <div className='halb-data'> + <div className='load-balancing-page-title'>{i18n('High Availability & Load Balancing')}</div> + <ValidationForm + onDataChanged={onQDataChanged} + data={qdata} schema={qschema} + isReadOnlyMode={isReadOnlyMode} + hasButtons={false}> + {pointers.map(pointer => this.renderTextAreaItem(pointer))} + </ValidationForm> + </div> + </div> + ); + } + + toggle(name) { + let st = this.state.expanded[name] ? true : false; + let newState = {...this.state}; + newState.expanded[name] = !st; + this.setState(newState); + } + + save() { + let {onSubmit, qdata} = this.props; + return onSubmit({qdata}); + } +} + +export default SoftwareProductComponentLoadBalancingView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js new file mode 100644 index 0000000000..ed7c5a116a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import SoftwareProductComponentsMonitoringView from './SoftwareProductComponentsMonitoringView.jsx'; +import SoftwareProductComponentsMonitoringAction from './SoftwareProductComponentsMonitoringActionHelper.js'; + + +export const mapStateToProps = ({softwareProduct}) => { + + let {softwareProductEditor: {data:currentVSP = {}}, softwareProductComponents: {monitoring}} = softwareProduct; + let {trapFilename, pollFilename} = monitoring; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP); + + return { + isReadOnlyMode, + trapFilename, + pollFilename + }; +}; + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onDropMibFileToUpload: (formData, type) => + SoftwareProductComponentsMonitoringAction.uploadSnmpFile(dispatch, { + softwareProductId, + componentId, + formData, + type + }), + + onDeleteSnmpFile: type => SoftwareProductComponentsMonitoringAction.deleteSnmpFile(dispatch, { + softwareProductId, + componentId, + type + }) + + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsMonitoringView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js new file mode 100644 index 0000000000..3faf571c09 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js @@ -0,0 +1,110 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import SoftwareProductComponentsMonitoringConstants, {actionTypes} from './SoftwareProductComponentsMonitoringConstants.js'; + +const UPLOAD = true; + +function baseUrl(vspId, componentId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${vspId}/components/${componentId}/monitors`; +} + +function snmpTrapUrl(vspId, componentId, isUpload) { + return `${baseUrl(vspId, componentId)}/snmp-trap${isUpload ? '/upload' : ''}`; +} + +function snmpPollUrl(vspId, componentId, isUpload) { + return `${baseUrl(vspId, componentId)}/snmp${isUpload ? '/upload' : ''}`; +} + +let onInvalidFileSizeUpload = (dispatch) => dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: { + title: i18n('Upload Failed'), + msg: i18n('no zip file was uploaded or zip file doesn\'t exist') + } +}); + +let uploadSnmpTrapFile = (dispatch, {softwareProductId, componentId, formData}) => { + RestAPIUtil.create(snmpTrapUrl(softwareProductId, componentId, UPLOAD), formData).then(()=> dispatch({ + type: actionTypes.SNMP_TRAP_UPLOADED, data: {filename: formData.get('upload').name} + })); +}; + +let uploadSnmpPollFile = (dispatch, {softwareProductId, componentId, formData}) => { + RestAPIUtil.create(snmpPollUrl(softwareProductId, componentId, UPLOAD), formData).then(()=> dispatch({ + type: actionTypes.SNMP_POLL_UPLOADED, data: {filename: formData.get('upload').name} + })); +}; + +let deleteSnmpTrapFile = (dispatch, {softwareProductId, componentId}) => { + RestAPIUtil.destroy(snmpTrapUrl(softwareProductId, componentId, !UPLOAD)).then(()=> dispatch({ + type: actionTypes.SNMP_TRAP_DELETED + })); +}; + +let deleteSnmpPollFile = (dispatch, {softwareProductId, componentId}) => { + RestAPIUtil.destroy(snmpPollUrl(softwareProductId, componentId, !UPLOAD)).then(()=> dispatch({ + type: actionTypes.SNMP_POLL_DELETED + })); +}; + +const SoftwareProductComponentsMonitoringAction = { + + fetchExistingFiles(dispatch, {softwareProductId, componentId}){ + RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/snmp`).then(response => + dispatch({ + type: actionTypes.SNMP_FILES_DATA_CHANGE, + data: {trapFilename: response.snmpTrap, pollFilename: response.snmpPoll} + }) + ); + }, + + uploadSnmpFile(dispatch, {softwareProductId, componentId, formData, type}){ + if (formData.get('upload').size) { + if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) { + uploadSnmpTrapFile(dispatch, {softwareProductId, componentId, formData}); + } + else { + uploadSnmpPollFile(dispatch, {softwareProductId, componentId, formData}); + } + } + else { + onInvalidFileSizeUpload(dispatch); + } + }, + + deleteSnmpFile(dispatch, {softwareProductId, componentId, type}){ + if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) { + deleteSnmpTrapFile(dispatch, {softwareProductId, componentId}); + } + else { + deleteSnmpPollFile(dispatch, {softwareProductId, componentId}); + } + } + +}; + +export default SoftwareProductComponentsMonitoringAction; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js new file mode 100644 index 0000000000..eeececb050 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + + SNMP_FILES_DATA_CHANGE: null, + + SNMP_TRAP_UPLOADED: null, + SNMP_POLL_UPLOADED: null, + + SNMP_TRAP_DELETED: null, + SNMP_POLL_DELETED: null +}); + +export default keyMirror({ + SNMP_TRAP: null, + SNMP_POLL: null +}); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js new file mode 100644 index 0000000000..72e0a85b10 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentsMonitoringConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.SNMP_FILES_DATA_CHANGE: + return { + ...state, + trapFilename: action.data.trapFilename, + pollFilename: action.data.pollFilename + }; + case actionTypes.SNMP_TRAP_UPLOADED: + return { + ...state, + trapFilename: action.data.filename + }; + case actionTypes.SNMP_POLL_UPLOADED: + return { + ...state, + pollFilename: action.data.filename + }; + case actionTypes.SNMP_TRAP_DELETED: + return { + ...state, + trapFilename: undefined + }; + case actionTypes.SNMP_POLL_DELETED: + return { + ...state, + pollFilename: undefined + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx new file mode 100644 index 0000000000..ca090c5f2f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx @@ -0,0 +1,117 @@ +import React, {Component, PropTypes} from 'react'; +import Dropzone from 'react-dropzone'; +import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js'; +import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar.js'; +import Button from 'react-bootstrap/lib/Button.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import SoftwareProductComponentsMonitoringConstants from './SoftwareProductComponentsMonitoringConstants.js'; + +class SoftwareProductComponentsMonitoringView extends Component { + + static propTypes = { + isReadOnlyMode: PropTypes.bool, + trapFilename: PropTypes.string, + pollFilename: PropTypes.string, + softwareProductId: PropTypes.string, + + onDropMibFileToUpload: PropTypes.func, + onDeleteSnmpFile: PropTypes.func + }; + + state = { + dragging: false + }; + + + render() { + return ( + <div className='vsp-component-monitoring'> + {this.renderDropzoneWithType(SoftwareProductComponentsMonitoringConstants.SNMP_TRAP)} + {this.renderDropzoneWithType(SoftwareProductComponentsMonitoringConstants.SNMP_POLL)} + </div> + ); + } + + renderDropzoneWithType(type) { + let {isReadOnlyMode, trapFilename, pollFilename} = this.props; + let fileName; + if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) { + fileName = trapFilename; + } + else { + fileName = pollFilename; + } + let refAndName = `fileInput${type.toString()}`; + let typeDisplayName = this.getFileTypeDisplayName(type); + return ( + <Dropzone + className={`snmp-dropzone ${this.state.dragging ? 'active-dragging' : ''}`} + onDrop={files => this.handleImport(files, {isReadOnlyMode, type, refAndName})} + onDragEnter={() => this.handleOnDragEnter(isReadOnlyMode)} + onDragLeave={() => this.setState({dragging:false})} + multiple={false} + disableClick={true} + ref={refAndName} + name={refAndName} + accept='.zip' + disabled> + <div className='draggable-wrapper'> + <div className='section-title'>{typeDisplayName}</div> + {fileName ? this.renderUploadedFileName(fileName, type) : this.renderUploadButton(refAndName)} + </div> + </Dropzone> + ); + } + + renderUploadButton(refAndName) { + let {isReadOnlyMode} = this.props; + return ( + <div + className={`software-product-landing-view-top-block-col-upl${isReadOnlyMode ? ' disabled' : ''}`}> + <div className='drag-text'>{i18n('Drag & drop for upload')}</div> + <div className='or-text'>{i18n('or')}</div> + <div className='upload-btn primary-btn' onClick={() => this.refs[refAndName].open()}> + <span className='primary-btn-text'>{i18n('Select file')}</span> + </div> + </div> + ); + } + + renderUploadedFileName(filename, type) { + return ( + <ButtonToolbar> + <ButtonGroup> + <Button disabled>{filename}</Button> + <Button className='delete-button' onClick={()=>this.props.onDeleteSnmpFile(type)}>X</Button> + </ButtonGroup> + </ButtonToolbar> + ); + } + + + handleOnDragEnter(isReadOnlyMode) { + if (!isReadOnlyMode) { + this.setState({dragging: true}); + } + } + + handleImport(files, {isReadOnlyMode, type, refAndName}) { + if (isReadOnlyMode) { + return; + } + + this.setState({dragging: false}); + let file = files[0]; + let formData = new FormData(); + formData.append('upload', file); + this.refs[refAndName].value = ''; + this.props.onDropMibFileToUpload(formData, type); + } + + getFileTypeDisplayName(type) { + return type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP ? 'SNMP Trap' : 'SNMP Poll'; + } + +} + +export default SoftwareProductComponentsMonitoringView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js new file mode 100644 index 0000000000..a412456e13 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentsNetworkActionHelper from './SoftwareProductComponentsNetworkActionHelper.js'; +import SoftwareProductComponentsNICEditorView from './SoftwareProductComponentsNICEditorView.jsx'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +export const mapStateToProps = ({softwareProduct}) => { + + let {softwareProductEditor: {data:currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents} = softwareProduct; + + let {network: {nicEditor = {}}} = softwareProductComponents; + let {data, qdata, qschema} = nicEditor; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + + return { + currentSoftwareProduct, + isValidityData, + data, + qdata, + qschema, + isReadOnlyMode + }; + +}; + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onDataChanged: deltaData => SoftwareProductComponentsNetworkActionHelper.updateNICData(dispatch, {deltaData}), + onSubmit: ({data, qdata}) => SoftwareProductComponentsNetworkActionHelper.saveNICDataAndQuestionnaire(dispatch, {softwareProductId, componentId, data, qdata}), + onCancel: () => SoftwareProductComponentsNetworkActionHelper.closeNICEditor(dispatch), + onQDataChanged: ({data}) => SoftwareProductComponentsNetworkActionHelper.updateNICQuestionnaire(dispatch, {data}) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductComponentsNICEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js new file mode 100644 index 0000000000..d49f9ccb1e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.NICEditor.OPEN: + return { + ...state, + data: action.nic + }; + case actionTypes.NICEditor.CLOSE: + return {}; + case actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE: + return { + ...state, + qdata: action.payload.qdata || state.qdata, + qschema: action.payload.qschema || state.qschema + }; + case actionTypes.NICEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx new file mode 100644 index 0000000000..7393a835dc --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx @@ -0,0 +1,322 @@ +import React from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; + +class SoftwareProductComponentsNetworkEditorView extends React.Component { + + render() { + let {onCancel, isReadOnlyMode} = this.props; + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => onCancel() } + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + className='vsp-components-network-editor'> + {this.renderEditorFields()} + </ValidationForm> + ); + } + + renderEditorFields() { + let {data = {}, qdata = {}, qschema = {}, onQDataChanged, onDataChanged, isReadOnlyMode} = this.props; + let {name, description, networkName} = data; + let netWorkValues = [{ + enum: networkName, + title: networkName + }]; + return( + <div className='editor-data'> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Name')} + value={name} + disabled={true} + type='text'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Purpose of NIC')} + value={description} + onChange={description => onDataChanged({description})} + disabled={isReadOnlyMode} + type='textarea'/> + </div> + </div> + <ValidationForm + onDataChanged={onQDataChanged} + data={qdata} + schema={qschema} + isReadOnlyMode={isReadOnlyMode} + hasButtons={false}> + <div className='row'> + <div className='part-title'>{i18n('Protocols')}</div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Protocols')} + type='select' + pointer='/protocols/protocols'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Protocol with Highest Traffic Profile')} + type='select' + pointer='/protocols/protocolWithHighestTrafficProfile'/> + </div> + </div> + <div className='row'> + <div className='part-title'>{i18n('IP Configuration')}</div> + <div className='col-md-3'> + <ValidationInput + label={i18n('IPv4 Required')} + type='checkbox' + pointer='/ipConfiguration/ipv4Required'/> + </div> + <div className='col-md-9'> + <ValidationInput + label={i18n('IPv6 Required')} + type='checkbox' + pointer='/ipConfiguration/ipv6Required'/> + </div> + </div> + </ValidationForm> + <div className='row'> + <div className='part-title'>{i18n('Network')}</div> + <div className='col-md-2'> + <ValidationInput + label={i18n('Internal')} + disabled + checked={true} + className='network-radio disabled' + type='radio'/> + </div> + <div className='col-md-4'> + <ValidationInput + label={i18n('External')} + disabled + checked={false} + className='network-radio disabled' + type='radio'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Network')} + type='select' + disabled={true} + values={netWorkValues}/> + </div> + </div> + <ValidationForm + onDataChanged={onQDataChanged} + data={qdata} + schema={qschema} + isReadOnlyMode={isReadOnlyMode} + hasButtons={false}> + <div className='row'> + <div className='part-title'>{i18n('Sizing')}</div> + <div className='col-md-12'> + <ValidationInput + label={i18n('Describe Quality of Service')} + type='textarea' + pointer='/sizing/describeQualityOfService'/> + </div> + </div> + + <div className='row'> + <div className='part-title'>{i18n('Inflow Traffic per second')}</div> + </div> + + <div className='row'> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Packets')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/inflowTrafficPerSecond/packets/peak'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/inflowTrafficPerSecond/packets/avg'/> + </div> + </div> + </div> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Bytes')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/inflowTrafficPerSecond/bytes/peak'/> + + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/inflowTrafficPerSecond/bytes/avg'/> + </div> + </div> + </div> + </div> + + <div className='row'> + <div className='part-title'>{i18n('Outflow Traffic per second')}</div> + </div> + + <div className='row'> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Packets')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/outflowTrafficPerSecond/packets/peak'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/outflowTrafficPerSecond/packets/avg'/> + + </div> + </div> + </div> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Bytes')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/outflowTrafficPerSecond/bytes/peak'/> + + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/outflowTrafficPerSecond/bytes/avg'/> + + </div> + </div> + </div> + </div> + + <div className='row'> + <div className='part-title'>{i18n('Flow Length')}</div> + </div> + + <div className='row'> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Packets')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/flowLength/packets/peak'/> + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/flowLength/packets/avg'/> + </div> + </div> + </div> + <div className='col-md-6'> + <div className='row'> + <div className='part-title-small'>{i18n('Bytes')}</div> + </div> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + label={i18n('Peak')} + type='text' + pointer='/sizing/flowLength/bytes/peak'/> + + </div> + <div className='col-md-6'> + <ValidationInput + label={i18n('Avg')} + type='text' + pointer='/sizing/flowLength/bytes/avg'/> + </div> + </div> + </div> + </div> + + <div className='row'> + <div className='col-md-9'> + <div className='row'> + <div className='part-title-small'>{i18n('Acceptable Jitter')}</div> + </div> + <div className='row'> + <div className='col-md-4'> + <ValidationInput + label={i18n('Min')} + type='text' + pointer='/sizing/acceptableJitter/mean'/> + </div> + <div className='col-md-4'> + <ValidationInput + label={i18n('Max')} + type='text' + pointer='/sizing/acceptableJitter/max'/> + </div> + <div className='col-md-4'> + <ValidationInput + label={i18n('Var')} + type='text' + pointer='/sizing/acceptableJitter/variable'/> + </div> + </div> + </div> + <div className='col-md-3'> + <div className='row'> + <div className='part-title-small'>{i18n('Acceptable Packet Loss %')}</div> + </div> + <div className='row'> + <div className='col-md-12'> + <ValidationInput + label={i18n('In Percent')} + type='text' + pointer='/sizing/acceptablePacketLoss'/> + </div> + </div> + </div> + </div> + </ValidationForm> + </div> + + ); + } + submit() { + let {data, qdata, onSubmit} = this.props; + onSubmit({data, qdata}); + } +} + +export default SoftwareProductComponentsNetworkEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js new file mode 100644 index 0000000000..bc53e1a7af --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.NIC_LIST_UPDATE: + return [...action.response]; + case actionTypes.NIC_LIST_EDIT: + const indexForEdit = state.findIndex(nic => nic.id === action.nic.id); + return [...state.slice(0, indexForEdit), action.nic, ...state.slice(indexForEdit + 1)]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js new file mode 100644 index 0000000000..8ff6b50189 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js @@ -0,0 +1,129 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js'; + +function baseUrl(softwareProductId, componentId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics`; +} + + +function fetchNICQuestionnaire({softwareProductId, componentId, nicId, version}) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/${nicId}/questionnaire${versionQuery}`); +} + +function fetchNIC({softwareProductId, componentId, nicId, version}) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/${nicId}${versionQuery}`); +} + +function fetchNICsList({softwareProductId, componentId, version}) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}${versionQuery}`); +} + +function saveNIC({softwareProductId, componentId, nic: {id, name, description, networkId}}) { + return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${id}`,{ + name, + description, + networkId + }); +} + +function saveNICQuestionnaire({softwareProductId, componentId, nicId, qdata}) { + return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${nicId}/questionnaire`, qdata); +} + +const SoftwareProductComponentNetworkActionHelper = { + + fetchNICsList(dispatch, {softwareProductId, componentId, version}) { + return fetchNICsList({softwareProductId, componentId, version}).then((response) => { + dispatch({ + type: actionTypes.NIC_LIST_UPDATE, + response: response.results + }); + }); + }, + + openNICEditor(dispatch, {nic = {}, data = {}}) { + dispatch({ + type: actionTypes.NICEditor.OPEN, + nic: {...data, id: nic.id} + }); + }, + + closeNICEditor(dispatch) { + dispatch({ + type: actionTypes.NICEditor.CLOSE + }); + }, + + loadNICData({softwareProductId, componentId, nicId, version}) { + return fetchNIC({softwareProductId, componentId, nicId, version}); + }, + + loadNICQuestionnaire(dispatch, {softwareProductId, componentId, nicId, version}) { + return fetchNICQuestionnaire({softwareProductId, componentId, nicId, version}).then((response) => { + dispatch({ + type: actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE, + payload: { + qdata: response.data ? JSON.parse(response.data) : {}, + qschema: JSON.parse(response.schema) + } + }); + }); + }, + + updateNICData(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.NICEditor.DATA_CHANGED, + deltaData + }); + }, + + updateNICQuestionnaire(dispatch, {data}) { + dispatch({ + type: actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE, + payload: { + qdata: data + } + }); + }, + + saveNICDataAndQuestionnaire(dispatch, {softwareProductId, componentId, data, qdata}) { + SoftwareProductComponentNetworkActionHelper.closeNICEditor(dispatch); + return Promise.all([ + saveNICQuestionnaire({softwareProductId, componentId, nicId: data.id, qdata}), + saveNIC({softwareProductId, componentId, nic: data}).then(() => { + dispatch({ + type: actionTypes.NIC_LIST_EDIT, + nic: data + }); + }) + ]); + } +}; + +export default SoftwareProductComponentNetworkActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js new file mode 100644 index 0000000000..193f4b20b5 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + NIC_LIST_EDIT: null, + NIC_LIST_UPDATE: null, + + NICEditor: { + OPEN: null, + CLOSE: null, + NIC_QUESTIONNAIRE_UPDATE: null, + DATA_CHANGED: null + } +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js new file mode 100644 index 0000000000..9172dc691a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js @@ -0,0 +1,86 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; +import SoftwareProductComponentsNetworkListView from './SoftwareProductComponentsNetworkListView.jsx'; +import SoftwareProductComponentsNetworkActionHelper from './SoftwareProductComponentsNetworkActionHelper.js'; + + +export const mapStateToProps = ({softwareProduct}) => { + + let {softwareProductEditor: {data: currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents} = softwareProduct; + let {network: {nicEditor = {}, nicList = []}, componentEditor: {data: componentData, qdata, qschema}} = softwareProductComponents; + let {data} = nicEditor; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + let {version} = currentSoftwareProduct; + let manualMode = nicList.length <= 0; + let isModalInEditMode = true; + + return { + version, + componentData, + qdata, + qschema, + isValidityData, + nicList, + isDisplayModal: Boolean(data), + isModalInEditMode, + manualMode, + isReadOnlyMode + }; + +}; + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}), + onAddNIC: () => SoftwareProductComponentsNetworkActionHelper.openNICEditor(dispatch), + onEditNicClick: (nic, version) => { + Promise.all([ + SoftwareProductComponentsNetworkActionHelper.loadNICData({ + softwareProductId, + componentId, + nicId: nic.id, + version + }), + SoftwareProductComponentsNetworkActionHelper.loadNICQuestionnaire(dispatch, { + softwareProductId, + componentId, + nicId: nic.id, + version + }) + ]).then( + ([{data}]) => SoftwareProductComponentsNetworkActionHelper.openNICEditor(dispatch, {nic, data}) + ); + }, + onSubmit: ({qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, + {softwareProductId, + vspComponentId: componentId, + qdata}); + } + + + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsNetworkListView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx new file mode 100644 index 0000000000..b3e17ff94b --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; +import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +import SoftwareProductComponentsNICEditor from './SoftwareProductComponentsNICEditor.js'; + +class SoftwareProductComponentsNetworkView extends React.Component { + + state = { + localFilter: '' + }; + + render() { + let {qdata, qschema, onQDataChanged, isModalInEditMode, isDisplayModal, softwareProductId, componentId, isReadOnlyMode} = this.props; + + return( + <div className='vsp-components-network'> + <div className='network-data'> + <div> + <ValidationForm + onDataChanged={onQDataChanged} + data={qdata} + isReadOnlyMode={isReadOnlyMode} + schema={qschema} + hasButtons={false}> + <h3 className='section-title'>{i18n('Network Capacity')}</h3> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + label={i18n('Protocol with Highest Traffic Profile across all NICs')} + type='select' + pointer='/network/networkCapacity/protocolWithHighestTrafficProfileAcrossAllNICs'/> + </div> + <div className='single-col add-line-break'> + <ValidationInput + label={i18n('Network Transactions per Second')} + type='text' + pointer='/network/networkCapacity/networkTransactionsPerSecond'/> + </div> + <div className='empty-two-col' /> + </div> + </div> + + </ValidationForm> + </div> + {this.renderNicList()} + </div> + <Modal show={isDisplayModal} bsSize='large' animation={true} className='network-nic-modal'> + <Modal.Header> + <Modal.Title>{isModalInEditMode ? i18n('Edit NIC') : i18n('Create New NIC')}</Modal.Title> + </Modal.Header> + <Modal.Body> + { + <SoftwareProductComponentsNICEditor + softwareProductId={softwareProductId} + componentId={componentId} + isReadOnlyMode={isReadOnlyMode}/> + } + </Modal.Body> + </Modal> + </div> + ); + } + + renderNicList() { + const {localFilter} = this.state; + let {onAddNIC, manualMode, isReadOnlyMode} = this.props; + let onAdd = manualMode ? onAddNIC : false; + return ( + <ListEditorView + title={i18n('Interfaces')} + plusButtonTitle={i18n('Add NIC')} + filterValue={localFilter} + placeholder={i18n('Filter NICs by Name')} + onAdd={onAdd} + isReadOnlyMode={isReadOnlyMode} + onFilter={filter => this.setState({localFilter: filter})}> + {!manualMode && this.filterList().map(nic => this.renderNicListItem(nic, isReadOnlyMode))} + </ListEditorView> + ); + } + + renderNicListItem(nic, isReadOnlyMode) { + let {id, name, description, networkName = ''} = nic; + let {onEditNicClick, version} = this.props; + return ( + <ListEditorItemView + key={id} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode} + onSelect={() => onEditNicClick(nic, version)}> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='name'>{name}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Purpose of NIC')}</div> + <div className='description'>{description}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Network')}</div> + <div className='artifact-name'>{networkName}</div> + </div> + + </ListEditorItemView> + ); + } + + filterList() { + let {nicList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return nicList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return nicList; + } + } + + save() { + let {onSubmit, qdata} = this.props; + return onSubmit({qdata}); + } +} + +export default SoftwareProductComponentsNetworkView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js new file mode 100644 index 0000000000..d535a34a82 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js @@ -0,0 +1,145 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import {actionTypes} from './SoftwareProductComponentProcessesConstants.js'; + +function baseUrl(softwareProductId, componentId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes`; +} + +function fetchProcessesList({softwareProductId, componentId, version}) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}${versionQuery}`); +} + +function deleteProcess({softwareProductId, componentId, processId}) { + return RestAPIUtil.destroy(`${baseUrl(softwareProductId, componentId)}/${processId}`); +} + +function putProcess({softwareProductId, componentId, process}) { + return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${process.id}`, { + name: process.name, + description: process.description + }); +} + +function postProcess({softwareProductId,componentId, process}) { + return RestAPIUtil.create(`${baseUrl(softwareProductId, componentId)}`, { + name: process.name, + description: process.description + }); +} + +function uploadFileToProcess({softwareProductId, processId, componentId, formData}) { + return RestAPIUtil.create(`${baseUrl(softwareProductId, componentId)}/${processId}/upload`, formData); +} + + + +const SoftwareProductComponentProcessesActionHelper = { + fetchProcessesList(dispatch, {softwareProductId, componentId, version}) { + dispatch({ + type: actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES, + processesList: [] + }); + + return fetchProcessesList({softwareProductId, componentId, version}).then(response => { + dispatch({ + type: actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES, + processesList: response.results + }); + }); + }, + + deleteProcess(dispatch, {process, softwareProductId, componentId}) { + return deleteProcess({softwareProductId, processId:process.id, componentId}).then(() => { + dispatch({ + type: actionTypes.DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS, + processId: process.id + }); + }); + + }, + + saveProcess(dispatch, {softwareProductId, componentId, previousProcess, process}) { + if (previousProcess) { + return putProcess({softwareProductId,componentId, process}).then(() => { + if (process.formData && process.formData.name !== previousProcess.artifactName){ + uploadFileToProcess({softwareProductId, processId: process.id, formData: process.formData, componentId}); + } + dispatch({ + type: actionTypes.EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS, + process + }); + }); + } + else { + return postProcess({softwareProductId, componentId, process}).then(response => { + if (process.formData) { + uploadFileToProcess({softwareProductId, processId: response.value, formData: process.formData, componentId}); + } + dispatch({ + type: actionTypes.ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS, + process: { + ...process, + id: response.value + } + }); + }); + } + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM, + processToDelete: false + }); + }, + + openDeleteProcessesConfirm(dispatch, {process} ) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM, + processToDelete: process + }); + }, + + openEditor(dispatch, process = {}) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN, + process + }); + }, + closeEditor(dispatch) { + dispatch({ + type:actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE + }); + }, + processEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.processEditor.DATA_CHANGED, + deltaData + }); + } +}; + +export default SoftwareProductComponentProcessesActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js new file mode 100644 index 0000000000..78a111a426 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null, + EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null, + DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null, + SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN: null, + SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE: null, + FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES: null, + SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM: null, + processEditor: { + DATA_CHANGED: null + } +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js new file mode 100644 index 0000000000..0138023c30 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper'; +import SoftwareProductComponentProcessesEditorView from './SoftwareProductComponentProcessesEditorView.jsx'; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductComponents: {componentProcesses = {}}} = softwareProduct; + let {processesList = [], processesEditor = {}} = componentProcesses; + let {data} = processesEditor; + + let previousData; + const processId = data ? data.id : null; + if(processId) { + previousData = processesList.find(process => process.id === processId); + } + + return { + data, + previousData + }; +}; + +const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => { + + return { + onDataChanged: deltaData => SoftwareProductComponentProcessesActionHelper.processEditorDataChanged(dispatch, {deltaData}), + onCancel: () => SoftwareProductComponentProcessesActionHelper.closeEditor(dispatch), + onSubmit: ({previousProcess, process}) => { + SoftwareProductComponentProcessesActionHelper.closeEditor(dispatch); + SoftwareProductComponentProcessesActionHelper.saveProcess(dispatch, {softwareProductId, previousProcess, componentId, process}); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductComponentProcessesEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js new file mode 100644 index 0000000000..f859f690e8 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentProcessesConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN: + return { + ...state, + data: action.process + }; + case actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE: + return {}; + + case actionTypes.processEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx new file mode 100644 index 0000000000..ca6d843af7 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx @@ -0,0 +1,124 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Dropzone from 'react-dropzone'; + + +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; + +const SoftwareProductProcessEditorPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + artifactName: React.PropTypes.string +}); + +class SoftwareProductProcessesEditorView extends React.Component { + + state = { + dragging: false, + files: [] + }; + + static propTypes = { + data: SoftwareProductProcessEditorPropType, + previousData: SoftwareProductProcessEditorPropType, + isReadOnlyMode: React.PropTypes.bool, + onDataChanged: React.PropTypes.func, + onSubmit: React.PropTypes.func, + onCancel: React.PropTypes.func + }; + + render() { + let {isReadOnlyMode, onCancel, onDataChanged, data = {}} = this.props; + let {name, description, artifactName} = data; + + return ( + <div> + <ValidationForm + ref='validationForm' + isReadOnlyMode={isReadOnlyMode} + hasButtons={true} + labledButtons={true} + onSubmit={ () => this.submit() } + onReset={ () => onCancel() } + className='vsp-processes-editor'> + <div className={`vsp-processes-editor-data${isReadOnlyMode ? ' disabled' : '' }`}> + <Dropzone + className={`vsp-process-dropzone-view ${this.state.dragging ? 'active-dragging' : ''}`} + onDrop={files => this.handleImportSubmit(files)} + onDragEnter={() => this.setState({dragging:true})} + onDragLeave={() => this.setState({dragging:false})} + multiple={false} + disableClick={true} + ref='processEditorFileInput' + name='processEditorFileInput' + accept='*.*'> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + onChange={name => onDataChanged({name})} + label={i18n('Name')} + value={name} + validations={{validateName: true, maxLength: 120, required: true}} + type='text'/> + <ValidationInput + label={i18n('Artifacts')} + value={artifactName} + type='text' + disabled/> + </div> + <div className='col-md-6'> + <div className='file-upload-box'> + <div className='drag-text'>{i18n('Drag & drop for upload')}</div> + <div className='or-text'>{i18n('or')}</div> + <div className='upload-btn primary-btn' onClick={() => this.refs.processEditorFileInput.open()}> + <span className='primary-btn-text'>{i18n('Select file')}</span> + </div> + </div> + </div> + </div> + <ValidationInput + onChange={description => onDataChanged({description})} + label={i18n('Notes')} + value={description} + name='vsp-process-description' + className='vsp-process-description' + validations={{maxLength: 1000}} + type='textarea'/> + </Dropzone> + </div> + </ValidationForm> + </div> + ); + } + + submit() { + const {data: process, previousData: previousProcess} = this.props; + let {files} = this.state; + let formData = new FormData(); + if (files.length) { + let file = files[0]; + formData.append('upload', file); + } + + let updatedProcess = { + ...process, + formData: files.length ? formData : false + }; + this.props.onSubmit({process: updatedProcess, previousProcess}); + } + + + handleImportSubmit(files) { + let {onDataChanged} = this.props; + this.setState({ + dragging: false, + complete: '0', + files + }); + onDataChanged({artifactName: files[0].name}); + } +} + +export default SoftwareProductProcessesEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js new file mode 100644 index 0000000000..5f6932897e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper.js'; + +import SoftwareProductComponentsProcessesListView from './SoftwareProductComponentsProcessesListView.jsx'; + +const mapStateToProps = ({softwareProduct}) => { + + let {softwareProductEditor: {data:currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents: {componentProcesses = {}}} = softwareProduct; + let{processesList = [], processesEditor = {}} = componentProcesses; + let {data} = processesEditor; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + + return { + currentSoftwareProduct, + isValidityData, + processesList, + isDisplayModal: Boolean(data), + isModalInEditMode: Boolean(data && data.id), + isReadOnlyMode + }; + +}; + +const mapActionsToProps = (dispatch, {softwareProductId}) => { + + return { + onAddProcess: () => SoftwareProductComponentProcessesActionHelper.openEditor(dispatch), + onEditProcessClick: (process) => SoftwareProductComponentProcessesActionHelper.openEditor(dispatch, process), + onDeleteProcessClick: (process) => SoftwareProductComponentProcessesActionHelper.openDeleteProcessesConfirm(dispatch, {process, softwareProductId}) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsProcessesListView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js new file mode 100644 index 0000000000..4bb124d52f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductComponentProcessesConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES: + return [...action.processesList]; + case actionTypes.EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: + const indexForEdit = state.findIndex(process => process.id === action.process.id); + return [...state.slice(0, indexForEdit), action.process, ...state.slice(indexForEdit + 1)]; + case actionTypes.ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: + return [...state, action.process]; + case actionTypes.DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: + return state.filter(process => process.id !== action.processId); + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx new file mode 100644 index 0000000000..48fa862364 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper.js'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +function renderMsg(processToDelete) { + let name = processToDelete ? processToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{name}"?', {name}); + return ( + <div> + <p>{msg}</p> + </div> + ); +}; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor, softwareProductComponents} = softwareProduct; + let {componentProcesses} = softwareProductComponents; + let {processToDelete} = componentProcesses; + let softwareProductId = softwareProductEditor.data.id; + const show = processToDelete !== false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: renderMsg(processToDelete), + confirmationDetails: {processToDelete, softwareProductId} + }; +}; + +const mapActionsToProps = (dispatch,{componentId, softwareProductId}) => { + return { + onConfirmed: ({processToDelete}) => { + SoftwareProductComponentProcessesActionHelper.deleteProcess(dispatch, {process: processToDelete, softwareProductId, componentId}); + SoftwareProductComponentProcessesActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + SoftwareProductComponentProcessesActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx new file mode 100644 index 0000000000..a8b07e9194 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx @@ -0,0 +1,125 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import SoftwareProductProcessesEditor from './SoftwareProductComponentProcessesEditor.js'; +import SoftwareProductComponentsProcessesConfirmationModal from './SoftwareProductComponentsProcessesConfirmationModal.jsx'; + +class SoftwareProductProcessesView extends React.Component { + + state = { + localFilter: '' + }; + + static propTypes = { + onAddProcess: React.PropTypes.func, + onEditProcessClick: React.PropTypes.func, + onDeleteProcessClick: React.PropTypes.func, + isDisplayModal: React.PropTypes.bool, + isModalInEditMode: React.PropTypes.bool, + onStorageSelect: React.PropTypes.func, + componentId: React.PropTypes.string, + softwareProductId: React.PropTypes.string + }; + + render() { + let { softwareProductId, componentId} = this.props; + + return ( + <div className='vsp-processes-page'> + <div className='software-product-view'> + <div className='software-product-landing-view-right-side flex-column'> + {this.renderEditor()} + {this.renderProcessList()} + </div> + <SoftwareProductComponentsProcessesConfirmationModal + componentId={componentId} + softwareProductId={softwareProductId}/> + </div> + </div> + ); + } + + renderEditor() { + let {softwareProductId, componentId, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props; + return ( + <Modal show={isDisplayModal} bsSize='large' animation={true}> + <Modal.Header> + <Modal.Title>{isModalInEditMode ? i18n('Edit Process Details') : i18n('Create New Process Details')}</Modal.Title> + </Modal.Header> + <Modal.Body className='edit-process-modal'> + <SoftwareProductProcessesEditor + componentId={componentId} + softwareProductId={softwareProductId} + isReadOnlyMode={isReadOnlyMode}/> + </Modal.Body> + </Modal> + + ); + } + + renderProcessList() { + const {localFilter} = this.state; + let {onAddProcess, isReadOnlyMode} = this.props; + return ( + <div className='processes-list'> + <ListEditorView + plusButtonTitle={i18n('Add Component Process Details')} + filterValue={localFilter} + placeholder={i18n('Filter Process')} + onAdd={onAddProcess} + isReadOnlyMode={isReadOnlyMode} + onFilter={filter => this.setState({localFilter: filter})}> + {this.filterList().map(processes => this.renderProcessListItem(processes, isReadOnlyMode))} + </ListEditorView> + </div> + ); + } + + renderProcessListItem(process, isReadOnlyMode) { + let {id, name, description, artifactName = ''} = process; + let {onEditProcessClick, onDeleteProcessClick} = this.props; + return ( + <ListEditorItemView + key={id} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode} + onSelect={() => onEditProcessClick(process)} + onDelete={() => onDeleteProcessClick(process)}> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='name'>{name}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Artifact name')}</div> + <div className='artifact-name'>{artifactName}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Notes')}</div> + <div className='description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + + filterList() { + let {processesList} = this.props; + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return processesList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return processesList; + } + } +} + +export default SoftwareProductProcessesView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js new file mode 100644 index 0000000000..fbd3f81ec2 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js @@ -0,0 +1,48 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; +import SoftwareProductComponentStorageView from './SoftwareProductComponentStorageView.jsx'; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct; + let {componentEditor: {data: componentData , qdata, qschema}} = softwareProductComponents; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP); + + return { + componentData, + qdata, + qschema, + isReadOnlyMode + }; +}; + +const mapActionToProps = (dispatch, {softwareProductId, componentId}) => { + return { + onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}), + onSubmit: ({qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});} + }; +}; + +export default connect(mapStateToProps, mapActionToProps, null, {withRef: true}) (SoftwareProductComponentStorageView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx new file mode 100644 index 0000000000..9c9600c376 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx @@ -0,0 +1,124 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx'; + + +class SoftwareProductComponentStorageView extends React.Component { + + static propTypes = { + componentId: React.PropTypes.string, + onQDataChanged: React.PropTypes.func, + onSubmit: React.PropTypes.func, + isReadOnlyMode: React.PropTypes.bool + }; + + render() { + let {qdata, qschema, onQDataChanged, onSubmit, isReadOnlyMode} = this.props; + + return( + <div className='vsp-component-questionnaire-view'> + <ValidationForm + ref='storageValidationForm' + hasButtons={false} + onSubmit={() => onSubmit({qdata})} + className='component-questionnaire-validation-form' + isReadOnlyMode={isReadOnlyMode} + onDataChanged={onQDataChanged} + data={qdata} + schema={qschema}> + + <div className='section-title'>{i18n('Backup')}</div> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <div className='vertical-flex'> + <label key='label' className='control-label'>{i18n('Backup Type')}</label> + <div className='radio-options-content-row'> + <ValidationInput + label={i18n('On Site')} + type='radiogroup' + pointer={'/storage/backup/backupType'} + className='radio-field'/> + </div> + </div> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Backup Solution')} + pointer={'/storage/backup/backupSolution'} + className='section-field'/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Backup Storage Size (GB)')} + pointer={'/storage/backup/backupStorageSize'} + className='section-field'/> + </div> + <ValidationInput + type='select' + label={i18n('Backup NIC')} + pointer={'/storage/backup/backupNIC'} + className='section-field'/> + </div> + </div> + + <div className='section-title'>{i18n('Snapshot Backup')}</div> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Snapshot Frequency (hours)')} + pointer={'/storage/snapshotBackup/snapshotFrequency'} + className='section-field'/> + </div> + <div className='empty-two-col' /> + <div className='empty-col' /> + </div> + </div> + + <div className='section-title'>{i18n('Log Backup')}</div> + <div className='rows-section'> + <div className='row-flex-components input-row'> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Size of Log Files (GB)')} + pointer={'/storage/logBackup/sizeOfLogFiles'} + className='section-field'/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Log Retention Period (days)')} + pointer={'/storage/logBackup/logRetentionPeriod'} + className='section-field'/> + </div> + <div className='single-col'> + <ValidationInput + type='text' + label={i18n('Log Backup Frequency (days)')} + pointer={'/storage/logBackup/logBackupFrequency'} + className='section-field'/> + </div> + <ValidationInput + type='text' + label={i18n('Log File Location')} + pointer={'/storage/logBackup/logFileLocation'} + className='section-field'/> + </div> + </div> + </ValidationForm> + </div> + ); + } + + save(){ + return this.refs.storageValidationForm.handleFormSubmit(new Event('dummy')); + } +} + +export default SoftwareProductComponentStorageView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js new file mode 100644 index 0000000000..46308f0045 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; +import SoftwareProductCreationActionHelper from './SoftwareProductCreationActionHelper.js'; +import SoftwareProductCreationView from './SoftwareProductCreationView.jsx'; + +const mapStateToProps = ({finalizedLicenseModelList, softwareProduct: {softwareProductCreation, softwareProductCategories} }) => { + return { + data: softwareProductCreation.data, + softwareProductCategories, + finalizedLicenseModelList + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onDataChanged: deltaData => SoftwareProductCreationActionHelper.changeData(dispatch, {deltaData}), + onCancel: () => SoftwareProductCreationActionHelper.resetData(dispatch), + onSubmit: (softwareProduct) => { + SoftwareProductCreationActionHelper.resetData(dispatch); + SoftwareProductCreationActionHelper.createSoftwareProduct(dispatch, {softwareProduct}).then(softwareProductId => { + let {vendorId: licenseModelId, licensingVersion} = softwareProduct; + OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, licensingVersion}); + }); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductCreationView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js new file mode 100644 index 0000000000..f4e51f198e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js @@ -0,0 +1,77 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; +import {actionTypes} from './SoftwareProductCreationConstants.js'; + + +function baseUrl() { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/`; +} + +function createSoftwareProduct(softwareProduct) { + return RestAPIUtil.create(baseUrl(), { + ...softwareProduct, + icon: 'icon', + licensingData: {} + }); +} + +const SoftwareProductCreationActionHelper = { + + open(dispatch) { + SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch); + dispatch({ + type: actionTypes.OPEN + }); + }, + + resetData(dispatch) { + dispatch({ + type: actionTypes.RESET_DATA + }); + }, + + changeData(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.DATA_CHANGED, + deltaData + }); + }, + + createSoftwareProduct(dispatch, {softwareProduct}) { + return createSoftwareProduct(softwareProduct).then(response => { + SoftwareProductActionHelper.addSoftwareProduct(dispatch, { + softwareProduct: { + ...softwareProduct, + id: response.vspId + } + }); + return response.vspId; + }); + } + +}; + +export default SoftwareProductCreationActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js new file mode 100644 index 0000000000..0a9cdb911c --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js @@ -0,0 +1,27 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + OPEN: null, + RESET_DATA: null, + DATA_CHANGED: null +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js new file mode 100644 index 0000000000..5e3db09e56 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductCreationConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.OPEN: + return { + ...state, + data: {}, + showModal: true + }; + case actionTypes.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + case actionTypes.RESET_DATA: + return {}; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx new file mode 100644 index 0000000000..2c8f243457 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx @@ -0,0 +1,123 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; + +import SoftwareProductCategoriesHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js'; + + +const SoftwareProductPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + category: React.PropTypes.string, + subCategory: React.PropTypes.string, + vendorId: React.PropTypes.string +}); + +class SoftwareProductCreationView extends React.Component { + + static propTypes = { + data: SoftwareProductPropType, + finalizedLicenseModelList: React.PropTypes.array, + softwareProductCategories: React.PropTypes.array, + onDataChanged: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired + }; + + render() { + let {softwareProductCategories, data = {}, onDataChanged, onCancel} = this.props; + let {name, description, vendorId, subCategory} = data; + + const vendorList = this.getVendorList(); + + return ( + <div className='software-product-creation-page'> + <ValidationForm + ref='validationForm' + hasButtons={true} + onSubmit={() => this.submit() } + onReset={() => onCancel() } + labledButtons={true}> + <div className='software-product-form-row'> + <div className='software-product-inline-section'> + <ValidationInput + value={name} + label={i18n('Name')} + ref='software-product-name' + onChange={name => onDataChanged({name})} + validations={{validateName: true, maxLength: 25, required: true}} + type='text' + className='field-section'/> + <ValidationInput + onEnumChange={vendorId => onDataChanged({vendorId})} + value={vendorId} + label={i18n('Vendor')} + values={vendorList} + validations={{required: true}} + type='select' + className='field-section'/> + <ValidationInput + label={i18n('Category')} + type='select' + value={subCategory} + onChange={subCategory => this.onSelectSubCategory(subCategory)} + validations={{required: true}} + className='options-input-category'> + <option key='' value=''>{i18n('please select…')}</option> + {softwareProductCategories.map(category => + category.subcategories && + <optgroup + key={category.name} + label={category.name}>{category.subcategories.map(sub => + <option key={sub.uniqueId} value={sub.uniqueId}>{`${sub.name} (${category.name})`}</option>)} + </optgroup>) + } + </ValidationInput> + </div> + <div className='software-product-inline-section'> + <ValidationInput + value={description} + label={i18n('Description')} + ref='description' + onChange={description => onDataChanged({description})} + validations={{freeEnglishText: true, maxLength: 1000, required: true}} + type='textarea' + className='field-section'/> + </div> + </div> + </ValidationForm> + </div> + ); + } + + getVendorList() { + let {finalizedLicenseModelList} = this.props; + + return [{enum: '', title: i18n('please select...')}].concat(finalizedLicenseModelList.map(vendor => { + return { + enum: vendor.id, + title: vendor.vendorName + }; + })); + } + + onSelectSubCategory(subCategory) { + let {softwareProductCategories, onDataChanged} = this.props; + let category = SoftwareProductCategoriesHelper.getCurrentCategoryOfSubCategory(subCategory, softwareProductCategories); + onDataChanged({category, subCategory}); + } + + create(){ + this.refs.validationForm.handleFormSubmit(new Event('dummy')); + } + + submit() { + const {data:softwareProduct, finalizedLicenseModelList} = this.props; + softwareProduct.vendorName = finalizedLicenseModelList.find(vendor => vendor.id === softwareProduct.vendorId).vendorName; + this.props.onSubmit(softwareProduct); + } +} + +export default SoftwareProductCreationView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js new file mode 100644 index 0000000000..16a100c664 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; +import SoftwareProductDetailsView from './SoftwareProductDetailsView.jsx'; + +export const mapStateToProps = ({finalizedLicenseModelList, softwareProduct, licenseModel: {licenseAgreement, featureGroup}}) => { + let {softwareProductEditor: {data: currentSoftwareProduct}, softwareProductCategories, softwareProductQuestionnaire} = softwareProduct; + let {licensingData = {}, licensingVersion} = currentSoftwareProduct; + let licenseAgreementList = [], filteredFeatureGroupsList = []; + if(licensingVersion && licensingVersion !== '') { + licenseAgreementList = licenseAgreement.licenseAgreementList; + let selectedLicenseAgreement = licenseAgreementList.find(la => la.id === licensingData.licenseAgreement); + if (selectedLicenseAgreement) { + let featureGroupsList = featureGroup.featureGroupsList.filter(({referencingLicenseAgreements}) => referencingLicenseAgreements.includes(selectedLicenseAgreement.id)); + if (featureGroupsList.length) { + filteredFeatureGroupsList = featureGroupsList.map(featureGroup => ({enum: featureGroup.id, title: featureGroup.name})); + } + } + } + let {qdata, qschema} = softwareProductQuestionnaire; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + + return { + currentSoftwareProduct, + softwareProductCategories, + licenseAgreementList, + featureGroupsList: filteredFeatureGroupsList, + finalizedLicenseModelList, + qdata, + qschema, + isReadOnlyMode + }; +}; + +export const mapActionsToProps = (dispatch) => { + return { + onDataChanged: deltaData => SoftwareProductActionHelper.softwareProductEditorDataChanged(dispatch, {deltaData}), + onVendorParamChanged: deltaData => SoftwareProductActionHelper.softwareProductEditorVendorChanged(dispatch, {deltaData}), + onQDataChanged: ({data}) => SoftwareProductActionHelper.softwareProductQuestionnaireUpdate(dispatch, {data}), + onValidityChanged: isValidityData => SoftwareProductActionHelper.setIsValidityData(dispatch, {isValidityData}), + onSubmit: (softwareProduct, qdata) =>{ return SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata});} + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductDetailsView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js new file mode 100644 index 0000000000..e060706b37 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.softwareProductEditor.OPEN: + return { + ...state, + data: {} + }; + case actionTypes.softwareProductEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + case actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION: + return { + ...state, + uploadData:action.uploadData + }; + case actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED: + return { + ...state, + isValidityData: action.isValidityData + }; + case actionTypes.softwareProductEditor.CLOSE: + return {}; + case actionTypes.SOFTWARE_PRODUCT_LOADED: + return { + ...state, + data: action.response + }; + case actionTypes.TOGGLE_NAVIGATION_ITEM: + return { + ...state, + mapOfExpandedIds: action.mapOfExpandedIds + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx new file mode 100644 index 0000000000..75a5797dec --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx @@ -0,0 +1,264 @@ +import React, {Component, PropTypes} from 'react'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Form from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; +import SoftwareProductCategoriesHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js'; + +class SoftwareProductDetails extends Component { + + static propTypes = { + vendorName: PropTypes.string, + currentSoftwareProduct: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + description: PropTypes.string, + category: PropTypes.string, + subCategory: PropTypes.string, + vendorId: PropTypes.string, + vendorName: PropTypes.string, + licensingVersion: PropTypes.string, + licensingData: PropTypes.shape({ + licenceAgreement: PropTypes.string, + featureGroups: PropTypes.array + }) + }), + softwareProductCategories: PropTypes.array, + finalizedLicenseModelList: PropTypes.array, + licenseAgreementList: PropTypes.array, + featureGroupsList: PropTypes.array, + onSubmit: PropTypes.func.isRequired, + onDataChanged: PropTypes.func.isRequired, + onValidityChanged: PropTypes.func.isRequired, + qdata: PropTypes.object.isRequired, + qschema: PropTypes.object.isRequired, + onQDataChanged: PropTypes.func.isRequired, + onVendorParamChanged: PropTypes.func.isRequired + }; + + state = { + licensingVersionsList: [] + }; + + render() { + let {softwareProductCategories, finalizedLicenseModelList, onDataChanged, featureGroupsList, licenseAgreementList, currentSoftwareProduct} = this.props; + let {name, description, vendorId, licensingVersion, subCategory, licensingData = {}} = currentSoftwareProduct; + let licensingVersionsList = this.state.licensingVersionsList.length > 0 ? this.state.licensingVersionsList : this.refreshVendorVersionsList(vendorId); + let {qdata, qschema, onQDataChanged} = this.props; + let {isReadOnlyMode} = this.props; + + return ( + <div className='vsp-details-page'> + <Form + ref='validationForm' + className='vsp-general-tab' + hasButtons={false} + onSubmit={() => this.props.onSubmit(currentSoftwareProduct, qdata)} + onValidityChanged={(isValidityData) => this.props.onValidityChanged(isValidityData)} + isReadOnlyMode={isReadOnlyMode}> + <div className='section-title general'>{i18n('General')}</div> + <div className='vsp-general-tab-inline-section'> + <div className='vsp-general-tab-sub-section'> + <ValidationInput + label={i18n('Name')} + type='text' + value={name} + onChange={name => onDataChanged({name})} + validations={{validateName: true, maxLength: 120, required: true}} + className='field-section'/> + <ValidationInput + label={i18n('Vendor')} + type='select' + selectedEnum={vendorId} + onEnumChange={vendorId => this.onVendorParamChanged({vendorId})} + className='field-section'> + {finalizedLicenseModelList.map(lm => <option key={lm.id} value={lm.id}>{lm.vendorName}</option>)} + </ValidationInput> + <div className='input-row'> + <ValidationInput + label={i18n('Category')} + type='select' + selectedEnum={subCategory} + onEnumChange={subCategory => this.onSelectSubCategory(subCategory)} + className='field-section'> + { + softwareProductCategories.map(category => + category.subcategories && + <optgroup + key={category.name} + label={category.name}>{category.subcategories.map(sub => + <option + key={sub.uniqueId} + value={sub.uniqueId}>{`${sub.name} (${category.name})`}</option>)} + </optgroup> + ) + } + </ValidationInput> + </div> + </div> + <div className='vsp-general-tab-sub-section input-row'> + <ValidationInput + label={i18n('Description')} + type='textarea' + value={description} + onChange={description => onDataChanged({description})} + className='field-section' + validations={{required: true}}/> + </div> + </div> + <div className='vsp-general-tab-section licenses'> + <div className='section-title'>{i18n('Licenses')}</div> + <div className='vsp-general-tab-inline-section input-row'> + <ValidationInput + onEnumChange={licensingVersion => this.onVendorParamChanged({vendorId, licensingVersion})} + selectedEnum={licensingVersion} + label={i18n('Licensing Version')} + values={licensingVersionsList} + type='select' + className='field-section'/> + <ValidationInput + label={i18n('License Agreement')} + type='select' + selectedEnum={licensingData.licenseAgreement} + className='field-section' + onEnumChange={(licenseAgreement) => this.onLicensingDataChanged({licenseAgreement, featureGroups: []})}> + <option key='placeholder' value=''>{i18n('Select...')}</option> + {licenseAgreementList.map(la => <option value={la.id} key={la.id}>{la.name}</option>)} + </ValidationInput> + </div> + <div className='vsp-general-tab-inline-section input-row'> + {licensingData.licenseAgreement && ( + <ValidationInput + type='select' + isMultiSelect={true} + onEnumChange={featureGroups => this.onFeatureGroupsChanged({featureGroups})} + multiSelectedEnum={licensingData.featureGroups} + name='feature-groups' + label={i18n('Feature Groups')} + clearable={false} + values={featureGroupsList}/>) + } + </div> + </div> + </Form> + <Form + data={qdata} + schema={qschema} + onDataChanged={onQDataChanged} + className='vsp-general-tab' + hasButtons={false} + isReadOnlyMode={isReadOnlyMode}> + <div className='vsp-general-tab-section'> + <div className='section-title'> {i18n('Availability')} </div> + <div className='vsp-general-tab-inline-section'> + <div className='vsp-general-tab-sub-section input-row'> + <ValidationInput + label={i18n('Use Availability Zones for High Availability')} + type='checkbox' + pointer='/general/availability/useAvailabilityZonesForHighAvailability'/> + </div> + </div> + <div className='section-title'> {i18n('Regions')} </div> + <div className='vsp-general-tab-inline-section'> + <div className='vsp-general-tab-sub-section input-row'> + <ValidationInput + type='select' + laebl='Ziv' + pointer='/general/regionsData/regions'/> + </div> + </div> + <div className='section-title'> {i18n('Storage Data Replication')} </div> + <div className='vsp-general-tab-inline-section'> + <div className='vsp-general-tab-sub-section'> + <ValidationInput + label={i18n('Storage Replication Size (GB)')} + type='text' + pointer='/general/storageDataReplication/storageReplicationSize' + className='field-section'/> + <ValidationInput + label={i18n('Storage Replication Source')} + type='text' + pointer='/general/storageDataReplication/storageReplicationSource' + className='field-section'/> + </div> + <div className='vsp-general-tab-sub-section'> + <ValidationInput + label={i18n('Storage Replication Frequency (minutes)')} + type='text' + pointer='/general/storageDataReplication/storageReplicationFrequency' + className='field-section'/> + <ValidationInput + label={i18n('Storage Replication Destination')} + type='text' + pointer='/general/storageDataReplication/storageReplicationDestination' + className='field-section'/> + </div> + </div> + </div> + </Form> + </div> + ); + } + + onVendorParamChanged({vendorId, licensingVersion}) { + let {finalizedLicenseModelList, onVendorParamChanged} = this.props; + if(!licensingVersion) { + const licensingVersionsList = this.refreshVendorVersionsList(vendorId); + licensingVersion = licensingVersionsList.length > 0 ? licensingVersionsList[0].enum : ''; + } + let vendorName = finalizedLicenseModelList.find(licenseModelItem => licenseModelItem.id === vendorId).vendorName || ''; + let deltaData = { + vendorId, + vendorName, + licensingVersion, + licensingData: {} + }; + onVendorParamChanged(deltaData); + } + + refreshVendorVersionsList(vendorId) { + if(!vendorId) { + return []; + } + + let {finalVersions} = this.props.finalizedLicenseModelList.find(vendor => vendor.id === vendorId); + + let licensingVersionsList = [{ + enum: '', + title: i18n('Select...') + }]; + if(finalVersions) { + finalVersions.forEach(version => licensingVersionsList.push({ + enum: version, + title: version + })); + } + + return licensingVersionsList; + } + + onSelectSubCategory(subCategory) { + let {softwareProductCategories, onDataChanged} = this.props; + let category = SoftwareProductCategoriesHelper.getCurrentCategoryOfSubCategory(subCategory, softwareProductCategories); + onDataChanged({category, subCategory}); + } + + onFeatureGroupsChanged({featureGroups}) { + this.onLicensingDataChanged({featureGroups}); + } + + onLicensingDataChanged(deltaData) { + this.props.onDataChanged({ + licensingData: { + ...this.props.currentSoftwareProduct.licensingData, + ...deltaData + } + }); + } + + save(){ + return this.refs.validationForm.handleFormSubmit(new Event('dummy')); + } +} + +export default SoftwareProductDetails; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js new file mode 100644 index 0000000000..7604f5841d --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; +import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js'; +import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; +import LandingPageView from './SoftwareProductLandingPageView.jsx'; + +const mapStateToProps = ({softwareProduct, licenseModel: {licenseAgreement}}) => { + let {softwareProductEditor: {data:currentSoftwareProduct = {}}, softwareProductComponents, softwareProductCategories = []} = softwareProduct; + let {licensingData = {}} = currentSoftwareProduct; + let {licenseAgreementList} = licenseAgreement; + let {componentsList} = softwareProductComponents; + let licenseAgreementName = licenseAgreementList.find(la => la.id === licensingData.licenseAgreement); + if (licenseAgreementName) { + licenseAgreementName = licenseAgreementName.name; + } + + let categoryName = '', subCategoryName = '', fullCategoryDisplayName = ''; + const category = softwareProductCategories.find(ca => ca.uniqueId === currentSoftwareProduct.category); + if (category) { + categoryName = category.name; + const subcategories = category.subcategories || []; + const subcat = subcategories.find(sc => sc.uniqueId === currentSoftwareProduct.subCategory); + subCategoryName = subcat && subcat.name ? subcat.name : ''; + } + fullCategoryDisplayName = `${subCategoryName} (${categoryName})`; + + const isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + + return { + currentSoftwareProduct: { + ...currentSoftwareProduct, + licenseAgreementName, + fullCategoryDisplayName + }, + isReadOnlyMode, + componentsList + }; +}; + +const mapActionsToProps = (dispatch, {version}) => { + return { + onDetailsSelect: ({id: softwareProductId, vendorId: licenseModelId}) => OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, { + softwareProductId, + licenseModelId + }), + onAttachmentsSelect: ({id: softwareProductId}) => OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId}), + onUpload: (softwareProductId, formData) => + SoftwareProductActionHelper.uploadFile(dispatch, { + softwareProductId, + formData, + failedNotificationTitle: i18n('Upload validation failed') + }), + onUploadConfirmation: (softwareProductId, formData) => + SoftwareProductActionHelper.uploadConfirmation(dispatch, { + softwareProductId, + formData, + failedNotificationTitle: i18n('Upload validation failed')}), + + onInvalidFileSizeUpload: () => dispatch({ + type: NotificationConstants.NOTIFY_ERROR, + data: { + title: i18n('Upload Failed'), + msg: i18n('no zip file was uploaded or zip file doesn\'t exist') + } + }), + onComponentSelect: ({id: softwareProductId, componentId}) => { + OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version }); + }, + /** for the next version */ + onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(LandingPageView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx new file mode 100644 index 0000000000..4a848834b2 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx @@ -0,0 +1,38 @@ +import {connect} from 'react-redux'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor} = softwareProduct; + let {uploadData} = softwareProductEditor; + const show = uploadData ? true : false; + return { + show, + title: 'Warning!', + type: 'warning', + msg: i18n('Upload will erase existing data. Do you want to continue?'), + confirmationDetails: {uploadData} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({uploadData}) => { + let {softwareProductId, formData, failedNotificationTitle} = uploadData; + SoftwareProductActionHelper.uploadFile(dispatch, { + softwareProductId, + formData, + failedNotificationTitle + }); + SoftwareProductActionHelper.hideUploadConfirm(dispatch); + }, + onDeclined: () => { + SoftwareProductActionHelper.hideUploadConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx new file mode 100644 index 0000000000..cf7c7a31a5 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx @@ -0,0 +1,272 @@ +import React from 'react'; +import classnames from 'classnames'; +import Dropzone from 'react-dropzone'; + + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import FontAwesome from 'react-fontawesome'; +import SoftwareProductLandingPageUploadConfirmationModal from './SoftwareProductLandingPageUploadConfirmationModal.jsx'; + + +const SoftwareProductPropType = React.PropTypes.shape({ + name: React.PropTypes.string, + description: React.PropTypes.string, + version: React.PropTypes.string, + id: React.PropTypes.string, + categoryId: React.PropTypes.string, + vendorId: React.PropTypes.string, + status: React.PropTypes.string, + licensingData: React.PropTypes.object, + validationData: React.PropTypes.object +}); + +const ComponentPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + displayName: React.PropTypes.string, + description: React.PropTypes.string +}); + +class SoftwareProductLandingPageView extends React.Component { + + state = { + localFilter: '', + fileName: '', + dragging: false, + files: [] + }; + + static propTypes = { + currentSoftwareProduct: SoftwareProductPropType, + isReadOnlyMode: React.PropTypes.bool, + componentsList: React.PropTypes.arrayOf(ComponentPropType), + onDetailsSelect: React.PropTypes.func, + onAttachmentsSelect: React.PropTypes.func, + onUpload: React.PropTypes.func, + onUploadConfirmation: React.PropTypes.func, + onInvalidFileSizeUpload: React.PropTypes.func, + onComponentSelect: React.PropTypes.func, + onAddComponent: React.PropTypes.func + }; + + render() { + let {currentSoftwareProduct, isReadOnlyMode, componentsList = []} = this.props; + return ( + <div className='software-product-landing-wrapper'> + <Dropzone + className={classnames('software-product-landing-view', {'active-dragging': this.state.dragging})} + onDrop={files => this.handleImportSubmit(files, isReadOnlyMode)} + onDragEnter={() => this.handleOnDragEnter(isReadOnlyMode)} + onDragLeave={() => this.setState({dragging:false})} + multiple={false} + disableClick={true} + ref='fileInput' + name='fileInput' + accept='.zip' + disabled> + <div className='draggable-wrapper'> + <div className='software-product-landing-view-top'> + <div className='row'> + {this.renderProductSummary(currentSoftwareProduct)} + {this.renderProductDetails(currentSoftwareProduct, isReadOnlyMode)} + </div> + </div> + </div> + </Dropzone> + { + componentsList.length > 0 && this.renderComponents() + } + <SoftwareProductLandingPageUploadConfirmationModal confirmationButtonText={i18n('Continue')}/> + </div> + ); + } + + handleOnDragEnter(isReadOnlyMode) { + if (!isReadOnlyMode) { + this.setState({dragging: true}); + } + } + + renderProductSummary(currentSoftwareProduct) { + let {name = '', description = '', vendorName = '', fullCategoryDisplayName = '', licenseAgreementName = ''} = currentSoftwareProduct; + let {onDetailsSelect} = this.props; + return ( + <div className='details-panel'> + <div className='software-product-landing-view-heading-title'>{i18n('Software Product Details')}</div> + <div + className='software-product-landing-view-top-block clickable' + onClick={() => onDetailsSelect(currentSoftwareProduct)}> + <div className='details-container'> + <div className='single-detail-section title-section'> + <div> + <div>{name}</div> + </div> + </div> + <div className='multiple-details-section'> + <div className='detail-col' > + <div className='title'>{i18n('Vendor')}</div> + <div className='description'>{vendorName}</div> + </div> + <div className='detail-col'> + <div className='title'>{i18n('Category')}</div> + <div className='description'>{fullCategoryDisplayName}</div> + </div> + <div className='detail-col'> + <div className='title extra-large'>{i18n('License Agreement')}</div> + <div className='description'> + {this.renderLicenseAgreement(licenseAgreementName)} + </div> + </div> + </div> + <div className='single-detail-section'> + <div className='title'>{i18n('Description')}</div> + <div className='description'>{description}</div> + </div> + </div> + </div> + </div> + ); + } + + renderProductDetails(currentSoftwareProduct, isReadOnlyMode) { + let {validationData} = currentSoftwareProduct; + let {onAttachmentsSelect} = this.props; + let details = { + heatTemplates: validationData ? '1' : '0', + images: '0', + otherArtifacts: '0' + }; + + return ( + <div className='details-panel'> + <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div> + <div className='software-product-landing-view-top-block'> + <div + className='software-product-landing-view-top-block-col' + onClick={() => onAttachmentsSelect(currentSoftwareProduct)}> + <div> + <div className='attachment-details'>{i18n('HEAT Templates')} (<span + className='attachment-details-count'>{details.heatTemplates}</span>) + </div> + <div className='attachment-details'>{i18n('Images')} (<span + className='attachment-details-count'>{details.images}</span>) + </div> + <div className='attachment-details'>{i18n('Other Artifacts')} (<span + className='attachment-details-count'>{details.otherArtifacts}</span>) + </div> + </div> + </div> + <div + className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}> + <div className='drag-text'>{i18n('Drag & drop for upload')}</div> + <div className='or-text'>{i18n('or')}</div> + <div className='upload-btn primary-btn' onClick={() => this.refs.fileInput.open()}> + <span className='primary-btn-text'>{i18n('Select file')}</span> + </div> + </div> + </div> + </div> + ); + } + + renderComponents() { + const {localFilter} = this.state; + + return ( + <ListEditorView + title={i18n('Virtual Function Components')} + filterValue={localFilter} + placeholder={i18n('Filter Components')} + onFilter={filter => this.setState({localFilter: filter})}> + {this.filterList().map(component => this.renderComponentsListItem(component))} + </ListEditorView> + ); + } + + renderComponentsListItem(component) { + let {id: componentId, name, displayName, description = ''} = component; + let {currentSoftwareProduct: {id}, onComponentSelect} = this.props; + return ( + <ListEditorItemView + key={name + Math.floor(Math.random() * (100 - 1) + 1).toString()} + className='list-editor-item-view' + onSelect={() => onComponentSelect({id, componentId})}> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Component')}</div> + <div className='name'>{displayName}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Description')}</div> + <div className='description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + renderLicenseAgreement(licenseAgreementName) { + if (!licenseAgreementName) { + return (<FontAwesome name='exclamation-triangle' className='warning-icon'/>); + } + return (licenseAgreementName); + } + + + filterList() { + let {componentsList = []} = this.props; + + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return componentsList.filter(({displayName = '', description = ''}) => { + return escape(displayName).match(filter) || escape(description).match(filter); + }); + } + else { + return componentsList; + } + } + + handleImportSubmit(files, isReadOnlyMode) { + if (isReadOnlyMode) { + return; + } + if (files[0] && files[0].size) { + this.setState({ + fileName: files[0].name, + dragging: false, + complete: '0', + }); + this.startUploading(files); + } + else { + this.props.onInvalidFileSizeUpload(); + } + + } + + startUploading(files) { + let {onUpload, currentSoftwareProduct, onUploadConfirmation} = this.props; + + let {validationData} = currentSoftwareProduct; + + if (!(files && files.length)) { + return; + } + let file = files[0]; + let formData = new FormData(); + formData.append('upload', file); + this.refs.fileInput.value = ''; + + if (validationData) { + onUploadConfirmation(currentSoftwareProduct.id, formData); + }else { + onUpload(currentSoftwareProduct.id, formData); + } + + } +} + +export default SoftwareProductLandingPageView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js new file mode 100644 index 0000000000..dadc7777e1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductNetworksView from './SoftwareProductNetworksView.jsx'; + +export const mapStateToProps = ({softwareProduct}) => { + let {softwareProductNetworks: {networksList = []}} = softwareProduct; + return {networksList}; +}; + +export default connect(mapStateToProps, null, null, {withRef: true})(SoftwareProductNetworksView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js new file mode 100644 index 0000000000..d0e29bcfe5 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductNetworksConstants.js'; +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +function baseUrl(svpId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${svpId}/networks`; +} + + +function fetchNetworksList(softwareProductId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`); +} + +const SoftwareProductNetworksActionHelper = { + fetchNetworksList(dispatch, {softwareProductId, version}) { + return fetchNetworksList(softwareProductId, version).then(response => { + dispatch({ + type: actionTypes.FETCH_SOFTWARE_PRODUCT_NETWORKS, + networksList: response.results + }); + }); + } +}; + +export default SoftwareProductNetworksActionHelper; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js new file mode 100644 index 0000000000..d428d21a26 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js @@ -0,0 +1,25 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + FETCH_SOFTWARE_PRODUCT_NETWORKS: null, +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js new file mode 100644 index 0000000000..0c9c62372a --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductNetworksConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.FETCH_SOFTWARE_PRODUCT_NETWORKS: + return [...action.networksList]; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx new file mode 100644 index 0000000000..bd47467fe1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +class SoftwareProductNetworksView extends React.Component { + + static propTypes = { + networksList: React.PropTypes.arrayOf(React.PropTypes.shape({ + id: React.PropTypes.string.isRequired, + name: React.PropTypes.string.isRequired, + dhcp: React.PropTypes.bool.isRequired + })).isRequired + }; + + state = { + localFilter: '' + }; + + render() { + const {localFilter} = this.state; + + return ( + <div className='vsp-networks-page'> + <ListEditorView + title={i18n('Networks')} + filterValue={localFilter} + placeholder={i18n('Filter Networks')} + onFilter={filter => this.setState({localFilter: filter})}> + {this.filterList().map(network => this.renderNetworksListItem(network))} + </ListEditorView> + </div> + ); + } + + renderNetworksListItem(network) { + let {id, name, dhcp} = network; + return ( + <ListEditorItemView + key={id} + className='list-editor-item-view' + isReadOnlyMode={true}> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='name'>{name}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('DHCP')}</div> + <div className='artifact-name'>{dhcp ? i18n('YES') : i18n('NO')}</div> + </div> + </ListEditorItemView> + ); + } + + filterList() { + let {networksList} = this.props; + + let {localFilter} = this.state; + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return networksList.filter(({name = ''}) => { + return escape(name).match(filter); + }); + } + else { + return networksList; + } + } +} + +export default SoftwareProductNetworksView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js new file mode 100644 index 0000000000..5c3a8dae01 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; + +import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper.js'; +import SoftwareProductProcessesView from './SoftwareProductProcessesView.jsx'; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor: {data: currentSoftwareProduct = {}}, softwareProductProcesses: {processesList, processesEditor}} = softwareProduct; + let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct); + let {data} = processesEditor; + + return { + currentSoftwareProduct, + processesList, + isDisplayEditor: Boolean(data), + isModalInEditMode: Boolean(data && data.id), + isReadOnlyMode + }; +}; + +const mapActionsToProps = (dispatch, {softwareProductId}) => { + return { + onAddProcess: () => SoftwareProductProcessesActionHelper.openEditor(dispatch), + onEditProcess: (process) => SoftwareProductProcessesActionHelper.openEditor(dispatch, process), + onDeleteProcess: (process) => SoftwareProductProcessesActionHelper.openDeleteProcessesConfirm(dispatch, {process, softwareProductId}) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductProcessesView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js new file mode 100644 index 0000000000..df5d08ffe5 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js @@ -0,0 +1,151 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductProcessesConstants.js'; +import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js'; +import Configuration from 'sdc-app/config/Configuration.js'; + +function baseUrl(svpId) { + const restPrefix = Configuration.get('restPrefix'); + return `${restPrefix}/v1.0/vendor-software-products/${svpId}/processes`; +} + +function putProcess(softwareProductId, process) { + return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${process.id}`, { + name: process.name, + description: process.description + }); +} + +function postProcess(softwareProductId, process) { + return RestAPIUtil.create(`${baseUrl(softwareProductId)}`, { + name: process.name, + description: process.description + }); +} + +function deleteProcess(softwareProductId, processId) { + return RestAPIUtil.destroy(`${baseUrl(softwareProductId)}/${processId}`); +} + +function uploadFileToProcess(softwareProductId, processId, formData) +{ + return RestAPIUtil.create(`${baseUrl(softwareProductId)}/${processId}/upload`, formData); +} + +function fetchProcesses(softwareProductId, version) { + let versionQuery = version ? `?version=${version}` : ''; + return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`); +} + + + +const SoftwareProductActionHelper = { + + fetchProcessesList(dispatch, {softwareProductId, version}) { + + dispatch({ + type: actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES, + processesList: [] + }); + + return fetchProcesses(softwareProductId, version).then(response => { + dispatch({ + type: actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES, + processesList: response.results + }); + }); + }, + openEditor(dispatch, process = {}) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN, + process + }); + }, + + deleteProcess(dispatch, {process, softwareProductId}) { + return deleteProcess(softwareProductId, process.id).then(() => { + dispatch({ + type: actionTypes.DELETE_SOFTWARE_PRODUCT_PROCESS, + processId: process.id + }); + }); + + }, + + closeEditor(dispatch) { + dispatch({ + type:actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE + }); + }, + + processEditorDataChanged(dispatch, {deltaData}) { + dispatch({ + type: actionTypes.processEditor.DATA_CHANGED, + deltaData + }); + }, + + saveProcess(dispatch, {softwareProductId, previousProcess, process}) { + if (previousProcess) { + return putProcess(softwareProductId, process).then(() => { + if (process.formData){ + uploadFileToProcess(softwareProductId, process.id, process.formData); + } + dispatch({ + type: actionTypes.EDIT_SOFTWARE_PRODUCT_PROCESS, + process + }); + }); + } + else { + return postProcess(softwareProductId, process).then(response => { + if (process.formData) { + uploadFileToProcess(softwareProductId, response.value, process.formData); + } + dispatch({ + type: actionTypes.ADD_SOFTWARE_PRODUCT_PROCESS, + process: { + ...process, + id: response.value + } + }); + }); + } + }, + + hideDeleteConfirm(dispatch) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM, + processToDelete: false + }); + }, + + openDeleteProcessesConfirm(dispatch, {process} ) { + dispatch({ + type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM, + processToDelete: process + }); + } + +}; + +export default SoftwareProductActionHelper; + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx new file mode 100644 index 0000000000..0159352dae --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx'; +import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper.js'; + +function renderMsg(processToDelete) { + let name = processToDelete ? processToDelete.name : ''; + let msg = i18n('Are you sure you want to delete "{name}"?', {name}); + return ( + <div> + <p>{msg}</p> + </div> + ); +}; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductEditor, softwareProductProcesses} = softwareProduct; + let {processToDelete} = softwareProductProcesses; + let softwareProductId = softwareProductEditor.data.id; + + const show = processToDelete !== false; + return { + show, + title: i18n('Warning!'), + type: 'warning', + msg: renderMsg(processToDelete), + confirmationDetails: {processToDelete, softwareProductId} + }; +}; + +const mapActionsToProps = (dispatch) => { + return { + onConfirmed: ({processToDelete, softwareProductId}) => { + SoftwareProductProcessesActionHelper.deleteProcess(dispatch, {process: processToDelete, softwareProductId}); + SoftwareProductProcessesActionHelper.hideDeleteConfirm(dispatch); + }, + onDeclined: () => { + SoftwareProductProcessesActionHelper.hideDeleteConfirm(dispatch); + } + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView); + diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js new file mode 100644 index 0000000000..63f3067a89 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import keyMirror from 'nfvo-utils/KeyMirror.js'; + +export const actionTypes = keyMirror({ + ADD_SOFTWARE_PRODUCT_PROCESS: null, + EDIT_SOFTWARE_PRODUCT_PROCESS: null, + DELETE_SOFTWARE_PRODUCT_PROCESS: null, + SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN: null, + SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE: null, + FETCH_SOFTWARE_PRODUCT_PROCESSES: null, + SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM: null, + processEditor: { + DATA_CHANGED: null + } +}); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js new file mode 100644 index 0000000000..8dc48c50b1 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {connect} from 'react-redux'; +import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper'; +import SoftwareProductProcessesEditorView from './SoftwareProductProcessesEditorView.jsx'; + +const mapStateToProps = ({softwareProduct}) => { + let {softwareProductProcesses: {processesList, processesEditor}} = softwareProduct; + let {data} = processesEditor; + + let previousData; + const processId = data ? data.id : null; + if(processId) { + previousData = processesList.find(process => process.id === processId); + } + + return { + data, + previousData + }; +}; + +const mapActionsToProps = (dispatch, {softwareProductId}) => { + return { + onDataChanged: deltaData => SoftwareProductProcessesActionHelper.processEditorDataChanged(dispatch, {deltaData}), + onSubmit: ({previousProcess, process}) => { + SoftwareProductProcessesActionHelper.closeEditor(dispatch); + SoftwareProductProcessesActionHelper.saveProcess(dispatch, {softwareProductId, previousProcess, process}); + }, + onClose: () => SoftwareProductProcessesActionHelper.closeEditor(dispatch) + }; +}; + +export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductProcessesEditorView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js new file mode 100644 index 0000000000..cae25e2c89 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductProcessesConstants.js'; + +export default (state = {}, action) => { + switch (action.type) { + case actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN: + return { + ...state, + data: action.process + }; + case actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE: + return {}; + + case actionTypes.processEditor.DATA_CHANGED: + return { + ...state, + data: { + ...state.data, + ...action.deltaData + } + }; + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx new file mode 100644 index 0000000000..c2c4aff382 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx @@ -0,0 +1,122 @@ +import React from 'react'; +import Dropzone from 'react-dropzone'; +import classnames from 'classnames'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; +import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx'; +import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx'; + +const SoftwareProductProcessEditorPropType = React.PropTypes.shape({ + id: React.PropTypes.string, + name: React.PropTypes.string, + description: React.PropTypes.string, + artifactName: React.PropTypes.string +}); + +class SoftwareProductProcessesEditorView extends React.Component { + + state = { + dragging: false, + files: [] + }; + + static propTypes = { + data: SoftwareProductProcessEditorPropType, + previousData: SoftwareProductProcessEditorPropType, + isReadOnlyMode: React.PropTypes.bool, + onDataChanged: React.PropTypes.func, + onSubmit: React.PropTypes.func, + onClose: React.PropTypes.func + }; + + render() { + let {data = {}, isReadOnlyMode, onDataChanged, onClose} = this.props; + let {name, description, artifactName} = data; + return ( + <ValidationForm + ref='validationForm' + hasButtons={true} + labledButtons={true} + isReadOnlyMode={isReadOnlyMode} + onSubmit={ () => this.submit() } + onReset={ () => onClose() } + className='vsp-processes-editor'> + <div className={classnames('vsp-processes-editor-data', {'disabled': isReadOnlyMode})}> + <Dropzone + className={classnames('vsp-process-dropzone-view', {'active-dragging': this.state.dragging})} + onDrop={files => this.handleImportSubmit(files)} + onDragEnter={() => this.setState({dragging: true})} + onDragLeave={() => this.setState({dragging: false})} + multiple={false} + disableClick={true} + ref='processEditorFileInput' + name='processEditorFileInput' + accept='*.*'> + <div className='row'> + <div className='col-md-6'> + <ValidationInput + onChange={name => onDataChanged({name})} + label={i18n('Name')} + value={name} + validations={{validateName: true, maxLength: 120, required: true}} + type='text'/> + <ValidationInput + label={i18n('Artifacts')} + value={artifactName} + type='text' + disabled/> + </div> + <div className='col-md-6'> + <div className='file-upload-box'> + <div className='drag-text'>{i18n('Drag & drop for upload')}</div> + <div className='or-text'>{i18n('or')}</div> + <div className='upload-btn primary-btn' onClick={() => this.refs.processEditorFileInput.open()}> + <span className='primary-btn-text'>{i18n('Select file')}</span> + </div> + </div> + </div> + </div> + <ValidationInput + onChange={description => onDataChanged({description})} + label={i18n('Notes')} + value={description} + name='vsp-process-description' + className='vsp-process-description' + validations={{maxLength: 1000}} + type='textarea'/> + </Dropzone> + </div> + </ValidationForm> + ); + } + + submit() { + const {data: process, previousData: previousProcess} = this.props; + let {files} = this.state; + let formData = false; + if (files.length) { + let file = files[0]; + formData = new FormData(); + formData.append('upload', file); + } + + let updatedProcess = { + ...process, + formData + }; + this.props.onSubmit({process: updatedProcess, previousProcess}); + } + + + handleImportSubmit(files) { + let {onDataChanged} = this.props; + this.setState({ + dragging: false, + complete: '0', + files + }); + onDataChanged({artifactName: files[0].name}); + } +} + +export default SoftwareProductProcessesEditorView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js new file mode 100644 index 0000000000..619a2dba0f --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {actionTypes} from './SoftwareProductProcessesConstants.js'; + +export default (state = [], action) => { + switch (action.type) { + case actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES: + return [...action.processesList]; + case actionTypes.EDIT_SOFTWARE_PRODUCT_PROCESS: + const indexForEdit = state.findIndex(process => process.id === action.process.id); + return [...state.slice(0, indexForEdit), action.process, ...state.slice(indexForEdit + 1)]; + case actionTypes.ADD_SOFTWARE_PRODUCT_PROCESS: + return [...state, action.process]; + case actionTypes.DELETE_SOFTWARE_PRODUCT_PROCESS: + return state.filter(process => process.id !== action.processId); + default: + return state; + } +}; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx new file mode 100644 index 0000000000..a2aa3d414e --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx @@ -0,0 +1,112 @@ +import React from 'react'; +import i18n from 'nfvo-utils/i18n/i18n.js'; +import Modal from 'nfvo-components/modal/Modal.jsx'; + +import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx'; + +import SoftwareProductProcessesEditor from './SoftwareProductProcessesEditor.js'; +import SoftwareProductProcessesConfirmationModal from './SoftwareProductProcessesConfirmationModal.jsx'; + + +class SoftwareProductProcessesView extends React.Component { + + state = { + localFilter: '' + }; + + static propTypes = { + onAddProcess: React.PropTypes.func.isRequired, + onEditProcess: React.PropTypes.func.isRequired, + onDeleteProcess: React.PropTypes.func.isRequired, + isDisplayEditor: React.PropTypes.bool.isRequired, + isReadOnlyMode: React.PropTypes.bool.isRequired + }; + + render() { + let { currentSoftwareProduct} = this.props; + return ( + <div className='software-product-landing-view-right-side vsp-processes-page'> + {this.renderEditor()} + {this.renderProcessList()} + <SoftwareProductProcessesConfirmationModal softwareProductId={currentSoftwareProduct.id}/> + </div> + ); + } + + renderEditor() { + let {currentSoftwareProduct: {id}, isModalInEditMode, isReadOnlyMode, isDisplayEditor} = this.props; + return ( + + <Modal show={isDisplayEditor} bsSize='large' animation={true}> + <Modal.Header> + <Modal.Title>{isModalInEditMode ? i18n('Edit Process Details') : i18n('Create New Process Details')}</Modal.Title> + </Modal.Header> + <Modal.Body className='edit-process-modal'> + <SoftwareProductProcessesEditor softwareProductId={id} isReadOnlyMode={isReadOnlyMode}/> + </Modal.Body> + </Modal> + ); + } + + renderProcessList() { + const {localFilter} = this.state; + let {onAddProcess, isReadOnlyMode} = this.props; + + return ( + <ListEditorView + plusButtonTitle={i18n('Add Process Details')} + filterValue={localFilter} + placeholder={i18n('Filter Process')} + onAdd={onAddProcess} + isReadOnlyMode={isReadOnlyMode} + onFilter={filter => this.setState({localFilter: filter})}> + {this.filterList().map(processes => this.renderProcessListItem(processes, isReadOnlyMode))} + </ListEditorView> + ); + } + + renderProcessListItem(process, isReadOnlyMode) { + let {id, name, description, artifactName = ''} = process; + let {onEditProcess, onDeleteProcess} = this.props; + return ( + <ListEditorItemView + key={id} + className='list-editor-item-view' + isReadOnlyMode={isReadOnlyMode} + onSelect={() => onEditProcess(process)} + onDelete={() => onDeleteProcess(process)}> + + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Name')}</div> + <div className='name'>{name}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Artifact name')}</div> + <div className='artifact-name'>{artifactName}</div> + </div> + <div className='list-editor-item-view-field'> + <div className='title'>{i18n('Notes')}</div> + <div className='description'>{description}</div> + </div> + </ListEditorItemView> + ); + } + + filterList() { + let {processesList} = this.props; + let {localFilter} = this.state; + + if (localFilter.trim()) { + const filter = new RegExp(escape(localFilter), 'i'); + return processesList.filter(({name = '', description = ''}) => { + return escape(name).match(filter) || escape(description).match(filter); + }); + } + else { + return processesList; + } + } +} + +export default SoftwareProductProcessesView; diff --git a/openecomp-ui/src/sdc-app/punch-outs.js b/openecomp-ui/src/sdc-app/punch-outs.js new file mode 100644 index 0000000000..46e84a60a4 --- /dev/null +++ b/openecomp-ui/src/sdc-app/punch-outs.js @@ -0,0 +1,29 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import '../../resources/scss/onboarding.scss'; +import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss'; + +import 'core-js/fn/array/includes.js'; +import OnboardingPunchOut from './onboarding/OnboardingPunchOut.jsx'; +import FlowsPunchOut from './flows/FlowsPunchOut.jsx'; + +PunchOutRegistry.register('onboarding/vendor', () => new OnboardingPunchOut()); +PunchOutRegistry.register('sequence-diagram', () => new FlowsPunchOut()); diff --git a/openecomp-ui/src/sdc-app/sdc.app.jsx b/openecomp-ui/src/sdc-app/sdc.app.jsx new file mode 100644 index 0000000000..0929fa0bb9 --- /dev/null +++ b/openecomp-ui/src/sdc-app/sdc.app.jsx @@ -0,0 +1,18 @@ +import '../../resources/scss/bootstrap.scss'; +import '../../resources/css/font-awesome.min.css'; +import 'react-select/dist/react-select.min.css'; +import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss'; +import '../../resources/scss/style.scss'; + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import Application from './Application.jsx'; +import Modules from './ModulesOptions.jsx'; + +//chrome 48 remove svg method which is used in jointjs core -> https://github.com/cpettitt/dagre-d3/issues/202 --> http://jointjs.com/blog/get-transform-to-element-polyfill.html +SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) { + return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM()); +}; + +ReactDOM.render(<Application><Modules/></Application>, document.getElementById('sdc-app')); diff --git a/openecomp-ui/test-utils/MockRest.js b/openecomp-ui/test-utils/MockRest.js new file mode 100644 index 0000000000..927da6030a --- /dev/null +++ b/openecomp-ui/test-utils/MockRest.js @@ -0,0 +1,85 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +const queue = { + fetch: [], + save: [], + create: [], + destroy: [] +}; + +const initQueue = () => { + queue['fetch'] = []; + queue['save'] = []; + queue['create'] = []; + queue['destroy'] = []; +}; + +const handleOperation = (handler, options) => { + if(typeof handler === 'function') { + return Promise.resolve(handler(options)); + } + else { + return Promise.resolve(handler); + } +}; + +export default { + + fetch(baseUrl, options) { + const {fetch} = queue; + if(!fetch.length) { + throw new Error(`Fetch operation was called without proper handler. baseUrl: '${baseUrl}' options: '${options}'`); + } + return handleOperation(fetch.shift(), {options, baseUrl}); + }, + + save(baseUrl, data, options) { + const {save} = queue; + if(!save.length) { + throw new Error(`Save operation was called without proper handler. baseUrl: '${baseUrl}' options: '${options}'`); + } + return handleOperation(save.shift(), {data, options, baseUrl}); + }, + + create(baseUrl, data, options) { + const {create} = queue; + if(!create.length) { + throw new Error(`Create operation was called without proper handler. baseUrl: '${baseUrl}' options: '${options}'`); + } + return handleOperation(create.shift(), {data, options, baseUrl}); + }, + + destroy(baseUrl, options) { + const {destroy} = queue; + if(!destroy.length) { + throw new Error(`Destroy operation was called without proper handler. baseUrl: '${baseUrl}' options: '${options}'`); + } + return handleOperation(destroy.shift(), {options, baseUrl}); + }, + + addHandler(operation, handler) { + queue[operation].push(handler); + }, + + resetQueue() { + initQueue(); + } +}; diff --git a/openecomp-ui/test-utils/Util.js b/openecomp-ui/test-utils/Util.js new file mode 100644 index 0000000000..7146267afe --- /dev/null +++ b/openecomp-ui/test-utils/Util.js @@ -0,0 +1,55 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import deepFreeze from 'deep-freeze'; +import ReactTestUtils from 'react-addons-test-utils'; + +//returned object should be treated as immutable. +export const cloneAndSet = (obj, path, value) => { + let retVal = {...obj}; + let inner = retVal; + + if (typeof path === 'string') { + path = path.split('.'); + } + + for (let i = 0; i < path.length - 1; i++) { + inner[path[i]] = { + ...inner[path[i]] + }; + inner = inner[path[i]]; + } + inner[path[path.length - 1]] = value; + return deepFreeze(retVal); +}; + +/** + * array findAllRenderedDOMComponentsWithTestId( + ReactComponent tree, + function test + ) + * @param tree - ReactComponent + * @param testId - string + * @returns {Array.<T>} + */ +export const findAllRenderedComponentsWithTestId = (tree, testId) => { + return ReactTestUtils.findAllInRenderedTree(tree, component => component.props.testId === testId); +}; + diff --git a/openecomp-ui/test/flows/FlowsListEditor.test.js b/openecomp-ui/test/flows/FlowsListEditor.test.js new file mode 100644 index 0000000000..534253567e --- /dev/null +++ b/openecomp-ui/test/flows/FlowsListEditor.test.js @@ -0,0 +1,279 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/flows/FlowsListEditor.js'; +import FlowsListEditorView from 'sdc-app/flows/FlowsListEditorView.jsx'; + +describe('Flows List Editor Mapper and View Classes: ', function () { + + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it('mapStateToProps mapper - without flowList', () => { + let flows = { + isDisplayModal: true, + isModalInEditMode: false, + shouldShowWorkflowsEditor: undefined + }; + let results = mapStateToProps({flows}); + expect(results.flowList).toExist(); + expect(results.flowList.length).toEqual(0); + expect(results.shouldShowWorkflowsEditor).toBe(true); + }); + + it('mapStateToProps mapper - populated flowList', () => { + let artifactName = 'test1', description = 'desc'; + let flows = { + flowList: [{artifactName, description}], + isDisplayModal: true, + isModalInEditMode: false, + shouldShowWorkflowsEditor: false + }; + let results = mapStateToProps({flows}); + expect(results.flowList).toExist(); + expect(results.flowList.length).toEqual(1); + expect(results.shouldShowWorkflowsEditor).toBe(false); + }); + + it('mapStateToProps mapper - populated flowList and currentFlow is in readonly', () => { + let artifactName = 'test1', description = 'desc'; + let currentFlow = {artifactName, description, readonly: true}; + let flows = { + flowList: [currentFlow], + currentFlow, + isDisplayModal: true, + isModalInEditMode: false, + shouldShowWorkflowsEditor: false + }; + let results = mapStateToProps({flows}); + expect(results.currentFlow).toExist(); + expect(results.isCheckedOut).toEqual(false); + }); + + it('mapStateToProps mapper - populated flowList and currentFlow is in not readonly', () => { + let artifactName = 'test1', description = 'desc'; + let currentFlow = {artifactName, description, readonly: false}; + let flows = { + flowList: [currentFlow], + currentFlow, + isDisplayModal: true, + isModalInEditMode: false, + shouldShowWorkflowsEditor: false + }; + let results = mapStateToProps({flows}); + expect(results.currentFlow).toExist(); + expect(results.isCheckedOut).toEqual(true); + }); + + it('basic view component run with empty flowList and should show the list', () => { + let renderer = TestUtils.createRenderer(); + let artifactName = 'test1', description = 'desc'; + let currentFlow = {artifactName, description, readonly: false}; + renderer.render(<FlowsListEditorView shouldShowWorkflowsEditor={true} flowList={[currentFlow]}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('basic view component run with empty flowList and should show the diagram', () => { + const flow = { + 'artifactType': 'WORKFLOW', + 'participants': [ + { + 'id': '1', + 'name': 'Customer' + }, + { + 'id': '2', + 'name': 'CCD' + }, + { + 'id': '3', + 'name': 'Infrastructure' + }, + { + 'id': '4', + 'name': 'MSO' + }, + { + 'id': '5', + 'name': 'SDN-C' + }, + { + 'id': '6', + 'name': 'A&AI' + }, + { + 'id': '7', + 'name': 'APP-C' + }, + { + 'id': '8', + 'name': 'Cloud' + }, + { + 'id': '9', + 'name': 'DCAE' + }, + { + 'id': '10', + 'name': 'ALTS' + }, + { + 'id': '11', + 'name': 'VF' + } + ], + 'serviceID': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b', + 'artifactDisplayName': 'zizizi', + 'artifactGroupType': 'INFORMATIONAL', + 'uniqueId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'artifactName': 'zizizi', + 'artifactLabel': 'zizizi', + 'artifactUUID': '0295a7cc-8c02-4105-9d7e-c30ce67ecd07', + 'artifactVersion': '1', + 'creationDate': 1470144601623, + 'lastUpdateDate': 1470144601623, + 'description': 'aslkjdfl asfdasdf', + 'mandatory': false, + 'timeout': 0, + 'esId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'artifactChecksum': 'NjBmYjc4NGM5MWIwNmNkMDhmMThhMDAwYmQxYjBiZTU=', + 'heatParameters': [], + 'sequenceDiagramModel': { + 'diagram': { + 'metadata': { + 'id': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'name': 'zizizi', + 'ref': 'BLANK' + }, + 'lifelines': [ + { + 'id': '1', + 'name': 'Customer', + 'index': 1, + 'x': 175 + }, + { + 'id': '2', + 'name': 'CCD', + 'index': 2, + 'x': 575 + }, + { + 'id': '3', + 'name': 'Infrastructure', + 'index': 3, + 'x': 975 + }, + { + 'id': '4', + 'name': 'MSO', + 'index': 4, + 'x': 1375 + }, + { + 'id': '5', + 'name': 'SDN-C', + 'index': 5, + 'x': 1775 + }, + { + 'id': '6', + 'name': 'A&AI', + 'index': 6, + 'x': 2175 + }, + { + 'id': '7', + 'name': 'APP-C', + 'index': 7, + 'x': 2575 + }, + { + 'id': '8', + 'name': 'Cloud', + 'index': 8, + 'x': 2975 + }, + { + 'id': '9', + 'name': 'DCAE', + 'index': 9, + 'x': 3375 + }, + { + 'id': '10', + 'name': 'ALTS', + 'index': 10, + 'x': 3775 + }, + { + 'id': '11', + 'name': 'VF', + 'index': 11, + 'x': 4175 + } + ], + 'steps': [ + { + 'message': { + 'id': '9377-5036-c011-cb95-3a8b-82c6-bbb5-bc84', + 'name': '[Unnamed Message]', + 'type': 'request', + 'from': '1', + 'to': '2', + 'index': 1 + } + }, + { + 'message': { + 'id': '64c4-4fd1-b1da-4355-a060-6e48-ee47-c85c', + 'name': '[Unnamed Message]', + 'type': 'request', + 'from': '1', + 'to': '2', + 'index': 2 + } + } + ] + } + } + }; + let renderer = TestUtils.createRenderer(); + renderer.render(<FlowsListEditorView currentFlow={flow} shouldShowWorkflowsEditor={false} flowList={[flow]}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('basic view component run with empty flowList and should show popup modal', () => { + let renderer = TestUtils.createRenderer(); + let artifactName = 'test1', description = 'desc'; + let currentFlow = {artifactName, description, readonly: false}; + renderer.render(<FlowsListEditorView isDisplayModal={true} shouldShowWorkflowsEditor={true} flowList={[currentFlow]}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + +}); diff --git a/openecomp-ui/test/flows/flowsEditorModal.test.js b/openecomp-ui/test/flows/flowsEditorModal.test.js new file mode 100644 index 0000000000..d8da97af4e --- /dev/null +++ b/openecomp-ui/test/flows/flowsEditorModal.test.js @@ -0,0 +1,89 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/flows/FlowsEditorModal.js'; +import FlowsEditorModalView from 'sdc-app/flows/FlowsEditorModalView.jsx'; + +describe('Flows Editor Modal Mapper and View Classes: ', function () { + + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it('mapStateToProps mapper - without currentFlow', () => { + var flows = { + serviceID: '123', + diagramType: 'SOME_TYPE' + }; + var results = mapStateToProps({flows}); + expect(results.currentFlow).toExist(); + expect(results.currentFlow.artifactName).toBe(''); + expect(results.currentFlow.description).toBe(''); + }); + + it('mapStateToProps mapper - populated currentFlow', () => { + let artifactName = 'test1', description = 'desc'; + var flows = { + currentFlow: {artifactName, description}, + serviceID: '123', + diagramType: 'SOME_TYPE' + }; + var results = mapStateToProps({flows}); + expect(results.currentFlow).toExist(); + expect(results.currentFlow.artifactName).toBe(artifactName); + expect(results.currentFlow.description).toBe(description); + expect(results.currentFlow.serviceID).toBe(flows.serviceID); + expect(results.currentFlow.artifactType).toBe(flows.diagramType); + }); + + it('basic modal view component run with empty artifact', () => { + let renderer = TestUtils.createRenderer(); + renderer.render( + <FlowsEditorModalView + onCancel={()=>{}} + onDataChanged={()=>{}} + currentFlow={{artifactName: '', description: ''}}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('modal view component run with data changed handler', done => { + let handler = () => done(); + let document = TestUtils.renderIntoDocument( + <FlowsEditorModalView + onCancel={()=>{}} + onDataChanged={handler} + currentFlow={{artifactName: '', description: ''}}/>); + let result = TestUtils.scryRenderedDOMComponentsWithTag(document, 'input'); + expect(result).toExist(); + expect(result.length).toExist(); + TestUtils.Simulate.change(result[0]); + }); + + it('modal view component - on save click', done => { + let handler = () => done(); + var flowsEditorModalView = new FlowsEditorModalView({currentFlow: {}, onSubmit: handler}); + flowsEditorModalView.onSaveClicked(); + }); + +}); diff --git a/openecomp-ui/test/flows/test.js b/openecomp-ui/test/flows/test.js new file mode 100644 index 0000000000..4c5ab78640 --- /dev/null +++ b/openecomp-ui/test/flows/test.js @@ -0,0 +1,497 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import store from 'sdc-app/AppStore.js'; +import FlowsActions from 'sdc-app/flows/FlowsActions.js'; +import {enums} from 'sdc-app/flows/FlowsConstants.js'; + +const NEW_FLOW = true; + +let assertFlowDataAfterCreateFetchAndUpdate = (data) => { + let {flowList, serviceID, diagramType} = store.getState().flows; + expect(serviceID).toBe(data.serviceID); + expect(diagramType).toBe(data.artifactType); + let uniqueId = data.uniqueId || `${data.serviceID}.${data.artifactName}`; + let index = flowList.findIndex(flow => flow.uniqueId === uniqueId); + expect(index).toNotBe(-1); +}; + +describe('Workflows and Management Flows Module Tests:', function () { + + + it('empty artifact should open flow creation modal', done => { + + const artifacts = {}; + + deepFreeze(store.getState()); + deepFreeze(artifacts); + FlowsActions.fetchFlowArtifacts(store.dispatch, { + artifacts, + diagramType: enums.WORKFLOW, + participants: [], + serviceID: '1234' + }); + setTimeout(() => { + let state = store.getState(); + expect(state.flows.isDisplayModal).toBe(true); + expect(state.flows.isModalInEditMode).toBe(false); + done(); + }, 50); + }); + + it('Close flow details editor modal', done => { + deepFreeze(store.getState()); + FlowsActions.closeFlowDetailsEditor(store.dispatch); + setTimeout(() => { + let state = store.getState(); + expect(state.flows.isDisplayModal).toBe(false); + expect(state.flows.isModalInEditMode).toBe(false); + done(); + }, 50); + }); + + it('Get Flows List from loaded artifact', done => { + + deepFreeze(store.getState()); + + const artifacts = { + 'test1': { + 'uniqueId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.test1', + 'artifactType': 'NETWORK_CALL_FLOW', + 'artifactName': 'test1', + 'artifactChecksum': 'MzYxZGIyNjlkNjRmMTM4ZWMxM2FjNDUyNDQwMTI3NzM=', + 'attUidLastUpdater': 'cs0008', + 'updaterFullName': 'Carlos Santana', + 'creationDate': 1468164899724, + 'lastUpdateDate': 1468164899724, + 'esId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.test1', + 'artifactLabel': 'test1', + 'artifactCreator': 'cs0008', + 'description': 'www', + 'mandatory': false, + 'artifactDisplayName': 'test1', + 'serviceApi': false, + 'artifactGroupType': 'INFORMATIONAL', + 'timeout': 0, + 'artifactVersion': '1', + 'artifactUUID': '28d4cb95-bb46-4666-b858-e333671e6444', + 'payloadUpdateDate': 1468164900232 + }, + 'kukuriku': { + 'uniqueId': '0280b577-2c7b-426e-b7a2-f0dc16508c37.kukuriku', + 'artifactType': 'PUPPET', + 'artifactName': 'fuel.JPG', + 'artifactChecksum': 'OWEyYTVjMWFiNWQ4ZDIwZDUxYTE3Y2EzZmI3YTYyMjA=', + 'attUidLastUpdater': 'cs0008', + 'updaterFullName': 'Carlos Santana', + 'creationDate': 1467877631512, + 'lastUpdateDate': 1467877631512, + 'esId': '0280b577-2c7b-426e-b7a2-f0dc16508c37.kukuriku', + 'artifactLabel': 'kukuriku', + 'artifactCreator': 'cs0008', + 'description': 'asdfasdf', + 'mandatory': false, + 'artifactDisplayName': 'kukuriku', + 'serviceApi': false, + 'artifactGroupType': 'INFORMATIONAL', + 'timeout': 0, + 'artifactVersion': '1', + 'artifactUUID': 'c1e98336-03f4-4b2a-b6a5-08eca44fe3c4', + 'payloadUpdateDate': 1467877632722 + }, + 'test3': { + 'uniqueId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.test3', + 'artifactType': 'NETWORK_CALL_FLOW', + 'artifactName': 'test3', + 'artifactChecksum': 'ZmJkZGU1M2M2ZWUxZTdmNGU5NTNiNTdiYTAzMmM1YzU=', + 'attUidLastUpdater': 'cs0008', + 'updaterFullName': 'Carlos Santana', + 'creationDate': 1468165068570, + 'lastUpdateDate': 1468165128827, + 'esId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.test3', + 'artifactLabel': 'test3', + 'artifactCreator': 'cs0008', + 'description': '333', + 'mandatory': false, + 'artifactDisplayName': 'test3', + 'serviceApi': false, + 'artifactGroupType': 'INFORMATIONAL', + 'timeout': 0, + 'artifactVersion': '2', + 'artifactUUID': '0988027c-d19c-43db-8315-2c68fc773775', + 'payloadUpdateDate': 1468165129335 + } + }; + + const artifactsArray = Object.keys(artifacts).map(artifact => artifact); + + deepFreeze(artifacts); + + deepFreeze(store.getState()); + + let actionData = { + artifacts, + diagramType: enums.WORKFLOW, + participants: [], + serviceID: '1234' + }; + FlowsActions.fetchFlowArtifacts(store.dispatch, actionData); + + setTimeout(() => { + let state = store.getState(); + expect(state.flows.isDisplayModal).toBe(false); + expect(state.flows.isModalInEditMode).toBe(false); + expect(state.flows.flowList.length).toEqual(artifactsArray.length); + expect(state.flows.flowParticipants).toEqual(actionData.participants); + expect(state.flows.serviceID).toBe(actionData.serviceID); + expect(state.flows.diagramType).toBe(actionData.diagramType); + done(); + }, 50); + + }); + + + it('Add New Flow', done => { + + deepFreeze(store.getState()); + + const flowCreateData = deepFreeze({ + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + description: 'aslkjdfl asfdasdf', + serviceID: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b', + }); + + + let expectedDataToBeSentInTheRequest = { + artifactGroupType: 'INFORMATIONAL', + artifactLabel: 'zizizi', + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + description: 'aslkjdfl asfdasdf', + payloadData: 'eyJWRVJTSU9OIjp7Im1ham9yIjoxLCJtaW5vciI6MH0sImRlc2NyaXB0aW9uIjoiYXNsa2pkZmwgYXNmZGFzZGYifQ==' + }; + mockRest.addHandler('create', ({data, baseUrl, options}) => { + expect(baseUrl).toBe(`/sdc1/feProxy/rest/v1/catalog/services/${flowCreateData.serviceID}/artifacts/`); + expect(data.artifactLabel).toBe(expectedDataToBeSentInTheRequest.artifactLabel); + expect(data.artifactName).toBe(expectedDataToBeSentInTheRequest.artifactName); + expect(data.artifactType).toBe(expectedDataToBeSentInTheRequest.artifactType); + expect(data.description).toBe(expectedDataToBeSentInTheRequest.description); + expect(data.payloadData).toBe(expectedDataToBeSentInTheRequest.payloadData); + expect(options.md5).toBe(true); + return { + artifactChecksum: 'NjBmYjc4NGM5MWIwNmNkMDhmMThhMDAwYmQxYjBiZTU=', + artifactCreator: 'cs0008', + artifactDisplayName: 'zizizi', + artifactGroupType: 'INFORMATIONAL', + artifactLabel: 'zizizi', + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + artifactUUID: '0295a7cc-8c02-4105-9d7e-c30ce67ecd07', + artifactVersion: '1', + attUidLastUpdater: 'cs0008', + creationDate: 1470144601623, + description: 'aslkjdfl asfdasdf', + esId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + lastUpdateDate: 1470144601623, + mandatory: false, + payloadUpdateDate: 1470144602131, + serviceApi: false, + timeout: 0, + uniqueId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + updaterFullName: 'Carlos Santana', + }; + }); + + FlowsActions.createOrUpdateFlow(store.dispatch, {flow: flowCreateData}, NEW_FLOW); + + setTimeout(() => { + assertFlowDataAfterCreateFetchAndUpdate(flowCreateData); + done(); + }, 50); + }); + + it('Fetch Flow', done => { + + deepFreeze(store.getState()); + + const flowFetchData = { + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + description: 'aslkjdfl asfdasdf', + serviceID: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b', + uniqueId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + participants: [] + }; + + mockRest.addHandler('fetch', ({baseUrl}) => { + //sdc1/feProxy/rest/v1/catalog/services/338d75f0-aec8-4eb4-89c9-8733fcd9bf3b/artifacts/338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi + expect(baseUrl).toBe(`/sdc1/feProxy/rest/v1/catalog/services/${flowFetchData.serviceID}/artifacts/${flowFetchData.uniqueId}`); + return { + artifactName: 'zizizi', + base64Contents: 'eyJWRVJTSU9OIjp7Im1ham9yIjoxLCJtaW5vciI6MH0sImRlc2NyaXB0aW9uIjoiYXNsa2pkZmwgYXNmZGFzZGYifQ==' + }; + }); + + FlowsActions.fetchArtifact(store.dispatch, {flow: flowFetchData}); + + setTimeout(() => { + assertFlowDataAfterCreateFetchAndUpdate(flowFetchData); + done(); + }, 50); + }); + + it('Update Existing Flow', done => { + + deepFreeze(store.getState()); + + const flowUpdateData = { + 'artifactType': 'WORKFLOW', + 'participants': [ + { + 'id': '1', + 'name': 'Customer' + }, + { + 'id': '2', + 'name': 'CCD' + }, + { + 'id': '3', + 'name': 'Infrastructure' + }, + { + 'id': '4', + 'name': 'MSO' + }, + { + 'id': '5', + 'name': 'SDN-C' + }, + { + 'id': '6', + 'name': 'A&AI' + }, + { + 'id': '7', + 'name': 'APP-C' + }, + { + 'id': '8', + 'name': 'Cloud' + }, + { + 'id': '9', + 'name': 'DCAE' + }, + { + 'id': '10', + 'name': 'ALTS' + }, + { + 'id': '11', + 'name': 'VF' + } + ], + 'serviceID': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b', + 'artifactDisplayName': 'zizizi', + 'artifactGroupType': 'INFORMATIONAL', + 'uniqueId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'artifactName': 'zizizi', + 'artifactLabel': 'zizizi', + 'artifactUUID': '0295a7cc-8c02-4105-9d7e-c30ce67ecd07', + 'artifactVersion': '1', + 'creationDate': 1470144601623, + 'lastUpdateDate': 1470144601623, + 'description': 'aslkjdfl asfdasdf', + 'mandatory': false, + 'timeout': 0, + 'esId': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'artifactChecksum': 'NjBmYjc4NGM5MWIwNmNkMDhmMThhMDAwYmQxYjBiZTU=', + 'heatParameters': [], + 'sequenceDiagramModel': { + 'diagram': { + 'metadata': { + 'id': '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + 'name': 'zizizi', + 'ref': 'BLANK' + }, + 'lifelines': [ + { + 'id': '1', + 'name': 'Customer', + 'index': 1, + 'x': 175 + }, + { + 'id': '2', + 'name': 'CCD', + 'index': 2, + 'x': 575 + }, + { + 'id': '3', + 'name': 'Infrastructure', + 'index': 3, + 'x': 975 + }, + { + 'id': '4', + 'name': 'MSO', + 'index': 4, + 'x': 1375 + }, + { + 'id': '5', + 'name': 'SDN-C', + 'index': 5, + 'x': 1775 + }, + { + 'id': '6', + 'name': 'A&AI', + 'index': 6, + 'x': 2175 + }, + { + 'id': '7', + 'name': 'APP-C', + 'index': 7, + 'x': 2575 + }, + { + 'id': '8', + 'name': 'Cloud', + 'index': 8, + 'x': 2975 + }, + { + 'id': '9', + 'name': 'DCAE', + 'index': 9, + 'x': 3375 + }, + { + 'id': '10', + 'name': 'ALTS', + 'index': 10, + 'x': 3775 + }, + { + 'id': '11', + 'name': 'VF', + 'index': 11, + 'x': 4175 + } + ], + 'steps': [ + { + 'message': { + 'id': '9377-5036-c011-cb95-3a8b-82c6-bbb5-bc84', + 'name': '[Unnamed Message]', + 'type': 'request', + 'from': '1', + 'to': '2', + 'index': 1 + } + }, + { + 'message': { + 'id': '64c4-4fd1-b1da-4355-a060-6e48-ee47-c85c', + 'name': '[Unnamed Message]', + 'type': 'request', + 'from': '1', + 'to': '2', + 'index': 2 + } + } + ] + } + } + }; + + mockRest.addHandler('create', ({baseUrl}) => { + expect(baseUrl).toBe(`/sdc1/feProxy/rest/v1/catalog/services/${flowUpdateData.serviceID}/artifacts/${flowUpdateData.uniqueId}`); + + return { + artifactChecksum: 'MmE5MWJmN2ZlN2FhM2JhMzA0NGQ1ODMyOWFhNWI0NDA=', + artifactCreator: 'cs0008', + artifactDisplayName: 'zizizi', + artifactGroupType: 'INFORMATIONAL', + artifactLabel: 'zizizi', + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + artifactUUID: '3319335b-969e-4d72-b5a2-409645de6d64', + artifactVersion: '3', + attUidLastUpdater: 'cs0008', + creationDate: 1470144601623, + description: 'aslkjdfl asfdasdf', + esId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + lastUpdateDate: 1470208425904, + mandatory: false, + payloadUpdateDate: 1470208426424, + serviceApi: false, + timeout: 0, + uniqueId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + updaterFullName: 'Carlos Santana' + }; + }); + + FlowsActions.createOrUpdateFlow(store.dispatch, {flow: flowUpdateData}, !NEW_FLOW); + + setTimeout(() => { + assertFlowDataAfterCreateFetchAndUpdate(flowUpdateData); + done(); + }, 50); + }); + + it('Delete Flow', done => { + + deepFreeze(store.getState()); + + const flowDeleteData = deepFreeze({ + artifactName: 'zizizi', + artifactType: 'WORKFLOW', + description: 'aslkjdfl asfdasdf', + serviceID: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b', + uniqueId: '338d75f0-aec8-4eb4-89c9-8733fcd9bf3b.zizizi', + participants: [] + }); + + mockRest.addHandler('destroy', ({baseUrl}) => { + expect(baseUrl).toBe(`/sdc1/feProxy/rest/v1/catalog/services/${flowDeleteData.serviceID}/artifacts/${flowDeleteData.uniqueId}`); + return {}; + }); + + FlowsActions.deleteFlow(store.dispatch, {flow: flowDeleteData}); + + setTimeout(() => { + let {flowList} = store.getState().flows; + let index = flowList.findIndex(flow => flow.uniqueId === flowDeleteData.uniqueId); + expect(index).toBe(-1); + done(); + }, 50); + }); + +}); + diff --git a/openecomp-ui/test/licenseModel/entitlementPools/test.js b/openecomp-ui/test/licenseModel/entitlementPools/test.js new file mode 100644 index 0000000000..32705385b4 --- /dev/null +++ b/openecomp-ui/test/licenseModel/entitlementPools/test.js @@ -0,0 +1,244 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import deepFreeze from 'deep-freeze'; +import {expect} from 'chai'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import EntitlementPoolsActionHelper from 'sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js'; + +describe('Entitlement Pools Module Tests', function () { + + const LICENSE_MODEL_ID = '555'; + + it('Load Entitlement Pools List', () => { + const entitlementPoolsList = [ + { + name: 'ep1', + description: 'string', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + sku: 'DEF2-385A-4521-AAAA', + id: '1', + referencingFeatureGroups: [], + partNumber: '51529' + } + ]; + deepFreeze(entitlementPoolsList); + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.entitlementPool.entitlementPoolsList', entitlementPoolsList); + + mockRest.addHandler('fetch', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/entitlement-pools`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return {results: entitlementPoolsList}; + }); + + return EntitlementPoolsActionHelper.fetchEntitlementPoolsList(store.dispatch, {licenseModelId: LICENSE_MODEL_ID}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Delete Entitlement Pool', () => { + const entitlementPoolsList = [ + { + name: 'ep1', + description: 'string', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + sku: 'DEF2-385A-4521-AAAA', + id: '1', + referencingFeatureGroups: [], + partNumber: '51529' + } + ]; + + deepFreeze(entitlementPoolsList); + const store = storeCreator({ + licenseModel: { + entitlementPool: { + entitlementPoolsList + } + } + }); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.entitlementPool.entitlementPoolsList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/entitlement-pools/${entitlementPoolsList[0].id}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return { + results: { + returnCode: 'OK' + } + }; + }); + + return EntitlementPoolsActionHelper.deleteEntitlementPool(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + entitlementPoolId: entitlementPoolsList[0].id + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add Entitlement Pool', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const entitlementPoolPostRequest = { + name: 'ep1', + description: 'string', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + manufacturerReferenceNumber: 'DEF2-385A-4521-AAAA', + }; + const entitlementPoolToAdd = { + name: 'ep1', + description: 'string', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + manufacturerReferenceNumber: 'DEF2-385A-4521-AAAA', + referencingFeatureGroups: [] + }; + const entitlementPoolIdFromResponse = 'ADDED_ID'; + const entitlementPoolAfterAdd = { + ...entitlementPoolToAdd, + id: entitlementPoolIdFromResponse + }; + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.entitlementPool.entitlementPoolsList', [entitlementPoolAfterAdd]); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/entitlement-pools`); + expect(data).to.deep.equal(entitlementPoolPostRequest); + expect(options).to.equal(undefined); + return { + returnCode: 'OK', + value: entitlementPoolIdFromResponse + }; + }); + + return EntitlementPoolsActionHelper.saveEntitlementPool(store.dispatch, + { + licenseModelId: LICENSE_MODEL_ID, + previousEntitlementPool: null, + entitlementPool: entitlementPoolToAdd + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update Entitlement Pool', () => { + const entitlementPoolsList = [{ + name: 'ep1', + id: '0', + description: 'string', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + manufacturerReferenceNumber: 'DEF2-385A-4521-AAAA' + }]; + deepFreeze(entitlementPoolsList); + + const store = storeCreator({ + licenseModel: { + entitlementPool: { + entitlementPoolsList + } + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedEntitlementPoolId = entitlementPoolsList[0].id; + const previousEntitlementPoolData = entitlementPoolsList[0]; + const entitlementPoolUpdateData = { + ...entitlementPoolsList[0], + name: 'ep1_UPDATED', + description: 'string_UPDATED' + }; + deepFreeze(entitlementPoolUpdateData); + + const entitlementPoolPutRequest = { + name: 'ep1_UPDATED', + description: 'string_UPDATED', + thresholdValue: 75, + thresholdUnits: '%', + entitlementMetric: {'choice': 'User', 'other': ''}, + increments: 'string', + aggregationFunction: {'choice': 'Average', 'other': ''}, + operationalScope: {'choices': ['Other'], 'other': 'blabla'}, + time: {'choice': 'Hour', 'other': ''}, + manufacturerReferenceNumber: 'DEF2-385A-4521-AAAA' + }; + deepFreeze(entitlementPoolPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.entitlementPool.entitlementPoolsList', [entitlementPoolUpdateData]); + + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/entitlement-pools/${toBeUpdatedEntitlementPoolId}`); + expect(data).to.deep.equal(entitlementPoolPutRequest); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return EntitlementPoolsActionHelper.saveEntitlementPool(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + previousEntitlementPool: previousEntitlementPoolData, + entitlementPool: entitlementPoolUpdateData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + +}); diff --git a/openecomp-ui/test/licenseModel/featureGroups/test.js b/openecomp-ui/test/licenseModel/featureGroups/test.js new file mode 100644 index 0000000000..d334ab758e --- /dev/null +++ b/openecomp-ui/test/licenseModel/featureGroups/test.js @@ -0,0 +1,212 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js'; + + +describe('Feature Groups Module Tests', function () { + + const LICENSE_MODEL_ID = '555'; + + it('Load Feature Groups List', () => { + const featureGroupsList = [ + { + name: 'fs1', + id: 0, + description: 'fs1-d', + licenseKeyGroupsIds: [1], + entitlementPoolsIds: [1], + refCount: 0 + } + ]; + deepFreeze(featureGroupsList); + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.featureGroup.featureGroupsList', featureGroupsList); + + mockRest.addHandler('fetch', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/feature-groups`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return {results: featureGroupsList}; + }); + + return FeatureGroupsActionHelper.fetchFeatureGroupsList(store.dispatch, {licenseModelId: LICENSE_MODEL_ID}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Delete Feature Group', () => { + const featureGroupsList = [ + { + name: 'fs1', + id: 0, + description: 'fs1-d', + licenseKeyGroupsIds: [1], + entitlementPoolsIds: [1], + refCount: 0 + } + ]; + deepFreeze(featureGroupsList); + const store = storeCreator({ + licenseModel: { + featureGroup: { + featureGroupsList + } + } + }); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.featureGroup.featureGroupsList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/feature-groups/${featureGroupsList[0].id}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return { + results: { + returnCode: 'OK' + } + }; + }); + + return FeatureGroupsActionHelper.deleteFeatureGroup(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + featureGroupId: featureGroupsList[0].id + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add Feature Group', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const featureGroupPostRequest = { + name: 'fs1', + description: 'fs1-d', + partNumber: '123', + addedLicenseKeyGroupsIds: [1], + addedEntitlementPoolsIds: [1] + }; + const featureGroupToAdd = { + name: 'fs1', + description: 'fs1-d', + partNumber: '123', + licenseKeyGroupsIds: [1], + entitlementPoolsIds: [1] + }; + const featureGroupIdFromResponse = 'ADDED_ID'; + const featureGroupAfterAdd = { + ...featureGroupToAdd, + id: featureGroupIdFromResponse + }; + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.featureGroup.featureGroupsList', [featureGroupAfterAdd]); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/feature-groups`); + expect(data).to.deep.equal(featureGroupPostRequest); + expect(options).to.equal(undefined); + return { + returnCode: 'OK', + value: featureGroupIdFromResponse + }; + }); + + return FeatureGroupsActionHelper.saveFeatureGroup(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + featureGroup: featureGroupToAdd + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update Feature Group', () => { + const featureGroupsList = [{ + name: 'fs1', + id: 0, + description: 'fs1-d', + partNumber: '123', + licenseKeyGroupsIds: [1], + entitlementPoolsIds: [1], + refCount: 0 + }]; + deepFreeze(featureGroupsList); + + const store = storeCreator({ + licenseModel: { + featureGroup: { + featureGroupsList + } + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedFeatureGroupId = featureGroupsList[0].id; + const previousFeatureGroupData = featureGroupsList[0]; + const featureGroupUpdateData = { + ...featureGroupsList[0], + name: 'fs_UPDATED', + description: 'description_UPDATED', + partNumber: '123_UPDATED', + licenseKeyGroupsIds: [7], + entitlementPoolsIds: [7] + }; + deepFreeze(featureGroupUpdateData); + + const featureGroupPutRequest = { + name: 'fs_UPDATED', + description: 'description_UPDATED', + partNumber: '123_UPDATED', + addedLicenseKeyGroupsIds: [7], + addedEntitlementPoolsIds: [7], + removedLicenseKeyGroupsIds: [1], + removedEntitlementPoolsIds: [1] + }; + deepFreeze(featureGroupPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.featureGroup.featureGroupsList', [featureGroupUpdateData]); + + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/feature-groups/${toBeUpdatedFeatureGroupId}`); + expect(data).to.deep.equal(featureGroupPutRequest); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return FeatureGroupsActionHelper.saveFeatureGroup(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + previousFeatureGroup: previousFeatureGroupData, + featureGroup: featureGroupUpdateData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + +}); diff --git a/openecomp-ui/test/licenseModel/licenseAgreement/test.js b/openecomp-ui/test/licenseModel/licenseAgreement/test.js new file mode 100644 index 0000000000..a6e8a3d363 --- /dev/null +++ b/openecomp-ui/test/licenseModel/licenseAgreement/test.js @@ -0,0 +1,205 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js'; + + +describe('License Agreement Module Tests', () => { + + const LICENSE_MODEL_ID = '777'; + + it('Load License Agreement List', () => { + const licenseAgreementList = [ + { + id: '0', + name: 'name0', + description: 'description0', + licenseTerm: 'licenseTerm0', + requirementsAndConstrains: 'req_and_constraints0', + featureGroupsIds: ['77'] + } + ]; + deepFreeze(licenseAgreementList); + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseAgreement.licenseAgreementList', licenseAgreementList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-agreements`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return {results: licenseAgreementList}; + }); + return LicenseAgreementActionHelper.fetchLicenseAgreementList(store.dispatch, {licenseModelId: LICENSE_MODEL_ID}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Delete License Agreement', () => { + const licenseAgreementList = [ + { + id: '0', + name: 'name0', + description: 'description0', + licenseTerm: 'licenseTerm0', + requirementsAndConstrains: 'req_and_constraints0', + featureGroupsIds: ['77'] + } + ]; + deepFreeze(licenseAgreementList); + const store = storeCreator({ + licenseModel: { + licenseAgreement: { + licenseAgreementList + } + } + }); + deepFreeze(store.getState()); + const toBeDeletedLicenseAgreementId = '0'; + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseAgreement.licenseAgreementList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-agreements/${toBeDeletedLicenseAgreementId}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + }); + + return LicenseAgreementActionHelper.deleteLicenseAgreement(store.dispatch, { + licenseAgreementId: toBeDeletedLicenseAgreementId, + licenseModelId: LICENSE_MODEL_ID + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add License Agreement', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + const licenseAgreementPostRequest = { + name: 'name_ADDED_LA', + description: 'description_ADDED_LA', + licenseTerm: 'licenseTerm_ADDED_LA', + requirementsAndConstrains: 'req_and_constraints_ADDED_LA', + addedFeatureGroupsIds: [] + }; + deepFreeze(licenseAgreementPostRequest); + + const licenseAgreementToAdd = { + name: 'name_ADDED_LA', + description: 'description_ADDED_LA', + licenseTerm: 'licenseTerm_ADDED_LA', + requirementsAndConstrains: 'req_and_constraints_ADDED_LA', + featureGroupsIds: [] + }; + deepFreeze(licenseAgreementToAdd); + + const licenseAgreementIdFromResponse = 'ADDED_ID'; + const licenseAgreementAfterAdd = { + ...licenseAgreementToAdd, + id: licenseAgreementIdFromResponse + }; + deepFreeze(licenseAgreementAfterAdd); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseAgreement.licenseAgreementList', [licenseAgreementAfterAdd]); + + mockRest.addHandler('create', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-agreements`); + expect(data).to.deep.equal(licenseAgreementPostRequest); + expect(options).to.equal(undefined); + return { + value: licenseAgreementIdFromResponse + }; + }); + + return LicenseAgreementActionHelper.saveLicenseAgreement(store.dispatch, { + licenseAgreement: licenseAgreementToAdd, + licenseModelId: LICENSE_MODEL_ID + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update License Agreement', () => { + const licenseAgreementList = [ + { + id: '0', + name: 'name0', + description: 'description0', + licenseTerm: 'licenseTerm0', + requirementsAndConstrains: 'req_and_constraints0', + featureGroupsIds: ['77'] + } + ]; + const store = storeCreator({ + licenseModel: { + licenseAgreement: { + licenseAgreementList + } + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedLicenseAgreementId = licenseAgreementList[0].id; + const previousLicenseAgreementData = licenseAgreementList[0]; + + const licenseAgreementUpdateData = { + ...licenseAgreementList[0], + name: 'name_UPDATED', + description: 'description_UPDATED', + licenseTerm: 'licenseTerm_UPDATED_LA', + requirementsAndConstrains: 'req_and_constraints_UPDATED_LA', + featureGroupsIds: ['update_id_1', 'update_id_2'] + }; + deepFreeze(licenseAgreementUpdateData); + + const licenseAgreementPutRequest = { + name: 'name_UPDATED', + description: 'description_UPDATED', + licenseTerm: 'licenseTerm_UPDATED_LA', + requirementsAndConstrains: 'req_and_constraints_UPDATED_LA', + addedFeatureGroupsIds: ['update_id_1', 'update_id_2'], + removedFeatureGroupsIds: ['77'] + }; + deepFreeze(licenseAgreementPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseAgreement.licenseAgreementList', [licenseAgreementUpdateData]); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-agreements/${toBeUpdatedLicenseAgreementId}`); + expect(data).to.deep.equal(licenseAgreementPutRequest); + expect(options).to.equal(undefined); + }); + + return LicenseAgreementActionHelper.saveLicenseAgreement(store.dispatch, { + licenseModelId: LICENSE_MODEL_ID, + previousLicenseAgreement: previousLicenseAgreementData, + licenseAgreement: licenseAgreementUpdateData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + +}); diff --git a/openecomp-ui/test/licenseModel/licenseKeyGroups/test.js b/openecomp-ui/test/licenseModel/licenseKeyGroups/test.js new file mode 100644 index 0000000000..944bd44e49 --- /dev/null +++ b/openecomp-ui/test/licenseModel/licenseKeyGroups/test.js @@ -0,0 +1,197 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; + +import LicenseKeyGroupsActionHelper from 'sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js'; + +describe('License Key Groups Module Tests', function () { + + const LICENSE_MODEL_ID = '555'; + it('Load License Key Group', () => { + const licenseKeyGroupsList = [ + { + name: 'lsk1', + description: 'string', + type: 'Unique', + operationalScope: {'choices': ['Data_Center'], 'other': ''}, + id: '0' + } + ]; + deepFreeze(licenseKeyGroupsList); + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseKeyGroup.licenseKeyGroupsList', licenseKeyGroupsList); + + mockRest.addHandler('fetch', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-key-groups`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return {results: licenseKeyGroupsList}; + }); + + return LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(store.dispatch, {licenseModelId: LICENSE_MODEL_ID}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Delete License Key Group', () => { + const licenseKeyGroupsList = [ + { + name: 'lsk1', + description: 'string', + type: 'Unique', + operationalScope: {'choices': ['Data_Center'], 'other': ''}, + id: '0' + } + ]; + deepFreeze(licenseKeyGroupsList); + const store = storeCreator({ + licenseModel: { + licenseKeyGroup: { + licenseKeyGroupsList + } + } + }); + deepFreeze(store.getState()); + const toBeDeletedLicenseKeyGroupId = '0'; + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseKeyGroup.licenseKeyGroupsList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-key-groups/${toBeDeletedLicenseKeyGroupId}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + }); + + return LicenseKeyGroupsActionHelper.deleteLicenseKeyGroup(store.dispatch, { + licenseKeyGroupId: toBeDeletedLicenseKeyGroupId, + licenseModelId: LICENSE_MODEL_ID + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add License Key Group', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const licenseKeyGroupPostRequest = { + name: 'lsk1_ADDED', + description: 'string_ADDED', + type: 'Unique_ADDED', + operationalScope: {'choices': ['Data_Center'], 'other': ''} + }; + deepFreeze(licenseKeyGroupPostRequest); + + const licenseKeyGroupToAdd = { + ...licenseKeyGroupPostRequest + }; + + deepFreeze(licenseKeyGroupToAdd); + + const licenseKeyGroupIdFromResponse = 'ADDED_ID'; + const licenseKeyGroupAfterAdd = { + ...licenseKeyGroupToAdd, + id: licenseKeyGroupIdFromResponse + }; + deepFreeze(licenseKeyGroupAfterAdd); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseKeyGroup.licenseKeyGroupsList', [licenseKeyGroupAfterAdd]); + + mockRest.addHandler('create', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-key-groups`); + expect(data).to.deep.equal(licenseKeyGroupPostRequest); + expect(options).to.equal(undefined); + return { + value: licenseKeyGroupIdFromResponse + }; + }); + + return LicenseKeyGroupsActionHelper.saveLicenseKeyGroup(store.dispatch, { + licenseKeyGroup: licenseKeyGroupToAdd, + licenseModelId: LICENSE_MODEL_ID + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update License Key Group', () => { + const licenseKeyGroupsList = [ + { + name: 'lsk1', + description: 'string', + type: 'Unique', + operationalScope: {'choices': ['Data_Center'], 'other': ''}, + id: '0' + } + ]; + deepFreeze(licenseKeyGroupsList); + const store = storeCreator({ + licenseModel: { + licenseKeyGroup: { + licenseKeyGroupsList + } + } + }); + + const toBeUpdatedLicenseKeyGroupId = licenseKeyGroupsList[0].id; + const previousLicenseKeyGroupData = licenseKeyGroupsList[0]; + + const licenseKeyGroupUpdateData = { + ...licenseKeyGroupsList[0], + name: 'lsk1_UPDATE', + description: 'string_UPDATE', + type: 'Unique', + operationalScope: {'choices': ['Data_Center'], 'other': ''} + }; + deepFreeze(licenseKeyGroupUpdateData); + + const licenseKeyGroupPutRequest = { + name: 'lsk1_UPDATE', + description: 'string_UPDATE', + type: 'Unique', + operationalScope: {'choices': ['Data_Center'], 'other': ''} + }; + deepFreeze(licenseKeyGroupPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModel.licenseKeyGroup.licenseKeyGroupsList', [licenseKeyGroupUpdateData]); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-license-models/${LICENSE_MODEL_ID}/license-key-groups/${toBeUpdatedLicenseKeyGroupId}`); + expect(data).to.deep.equal(licenseKeyGroupPutRequest); + expect(options).to.equal(undefined); + }); + + return LicenseKeyGroupsActionHelper.saveLicenseKeyGroup(store.dispatch, { + previousLicenseKeyGroup: previousLicenseKeyGroupData, + licenseKeyGroup: licenseKeyGroupUpdateData, + licenseModelId: LICENSE_MODEL_ID + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + +}); diff --git a/openecomp-ui/test/licenseModel/test.js b/openecomp-ui/test/licenseModel/test.js new file mode 100644 index 0000000000..c21d18f146 --- /dev/null +++ b/openecomp-ui/test/licenseModel/test.js @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import LicenseModelCreationActionHelper from 'sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js'; + +describe('License Model Module Tests', function () { + it('Add License Model', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const licenseModelPostRequest = deepFreeze({ + vendorName: 'vlm1', + description: 'string', + iconRef: 'icon' + }); + + const licenseModelToAdd = deepFreeze({ + ...licenseModelPostRequest + }); + + const licenseModelIdFromResponse = 'ADDED_ID'; + const licenseModelAfterAdd = deepFreeze({ + ...licenseModelToAdd, + id: licenseModelIdFromResponse + }); + + const expectedStore = cloneAndSet(store.getState(), 'licenseModelList', [licenseModelAfterAdd]); + + mockRest.addHandler('create', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal('/onboarding-api/v1.0/vendor-license-models/'); + expect(data).to.deep.equal(licenseModelPostRequest); + expect(options).to.equal(undefined); + return { + value: licenseModelIdFromResponse + }; + }); + + return LicenseModelCreationActionHelper.createLicenseModel(store.dispatch, { + licenseModel: licenseModelToAdd + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); +}); diff --git a/openecomp-ui/test/nfvo-components/input/dualListBox/dualListbox.test.js b/openecomp-ui/test/nfvo-components/input/dualListBox/dualListbox.test.js new file mode 100644 index 0000000000..eaa06eedf4 --- /dev/null +++ b/openecomp-ui/test/nfvo-components/input/dualListBox/dualListbox.test.js @@ -0,0 +1,94 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import DualListboxView from 'nfvo-components/input/dualListbox/DualListboxView.jsx'; + +const ITEMS = [{id: '1', name: 'aaa'}, {id: '2', name: 'bbb'}, {id: '3', name: 'ccc'}]; + +describe('dualListBox Module Tests', function () { + + + it('should render basically', () => { + var renderer = TestUtils.createRenderer(); + renderer.render(<DualListboxView onChange={()=>{}}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('should render with available list and 4 control buttons', () => { + var view = TestUtils.renderIntoDocument(<DualListboxView availableList={ITEMS} onChange={()=>{}}/>); + expect(view).toExist(); + var results = TestUtils.scryRenderedDOMComponentsWithClass(view, 'dual-list-option'); + expect(results.length).toBe(4); + }); + + it('should add item to selected list', done => { + const newItemValue = 'new item'; + let onChange = (value)=> { + expect(value).toEqual(newItemValue); + done(); + }; + var view = new DualListboxView({availableList:ITEMS, onChange, selectedValuesList:[]}); + expect(view).toExist(); + view.refs = { + availableValues: {getValue(){return newItemValue;}} + }; + view.addToSelectedList(); + }); + + it('should remove item from selected list', done => { + const selectedValuesList = ['a','b']; + let onChange = (value)=> { + expect(value).toEqual(selectedValuesList[1]); + done(); + }; + var view = new DualListboxView({availableList:ITEMS, onChange, selectedValuesList}); + expect(view).toExist(); + view.refs = { + selectedValues: {getValue(){return ['a'];}} + }; + view.removeFromSelectedList(); + }); + + it('should add all items to selected list', done => { + let onChange = (value)=> { + expect(value).toEqual(ITEMS.map(item => item.id)); + done(); + }; + var view = new DualListboxView({availableList:ITEMS, onChange, selectedValuesList:[]}); + expect(view).toExist(); + view.addAllToSelectedList(); + }); + + it('should remove all items from selected list', done => { + let onChange = (value)=> { + expect(value.length).toBe(0); + done(); + }; + var view = new DualListboxView({availableList:ITEMS, onChange, selectedValuesList:[]}); + expect(view).toExist(); + view.removeAllFromSelectedList(); + }); + + +}); diff --git a/openecomp-ui/test/nfvo-components/listEditor/listEditor.test.js b/openecomp-ui/test/nfvo-components/listEditor/listEditor.test.js new file mode 100644 index 0000000000..a3b098f611 --- /dev/null +++ b/openecomp-ui/test/nfvo-components/listEditor/listEditor.test.js @@ -0,0 +1,96 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import ListEditorView from 'src/nfvo-components/listEditor/ListEditorView.jsx'; +import ListEditorItemView from 'src/nfvo-components/listEditor/ListEditorItemView.jsx'; + +describe('listEditor Module Tests', function () { + + + it('list editor view should exist', () => { + expect(ListEditorView).toExist(); + }); + + it('list editor item view should exist', () => { + expect(ListEditorItemView).toExist(); + }); + + it('should render list and list item and call onEdit', done => { + let itemView = TestUtils.renderIntoDocument( + <ListEditorView title='some title'> + <ListEditorItemView onEdit={done}> + <div></div> + </ListEditorItemView> + </ListEditorView> + ); + expect(itemView).toExist(); + let sliderIcon = TestUtils.findRenderedDOMComponentWithClass(itemView, 'fa-sliders'); + TestUtils.Simulate.click(sliderIcon); + }); + + it('should render list and list item and call onFilter', done => { + let itemView = TestUtils.renderIntoDocument( + <ListEditorView onFilter={()=>{done();}}> + <ListEditorItemView> + <div></div> + </ListEditorItemView> + </ListEditorView> + ); + expect(itemView).toExist(); + let filterInput = TestUtils.findRenderedDOMComponentWithTag(itemView, 'input'); + TestUtils.Simulate.change(filterInput); + }); + + it('should render READONLY list item and not call onEdit', done => { + let itemView = TestUtils.renderIntoDocument( + <ListEditorItemView onEdit={done} isReadOnlyMode={true}> + <div></div> + </ListEditorItemView> + ); + expect(itemView).toExist(); + let sliderIcon = TestUtils.findRenderedDOMComponentWithClass(itemView, 'fa-sliders'); + TestUtils.Simulate.click(sliderIcon); + }); + + it('should render list item and call onDelete', done => { + let itemView = TestUtils.renderIntoDocument( + <ListEditorItemView onDelete={done} isReadOnlyMode={false}> + <div></div> + </ListEditorItemView> + ); + expect(itemView).toExist(); + let sliderIcon = TestUtils.findRenderedDOMComponentWithClass(itemView, 'fa-trash-o'); + TestUtils.Simulate.click(sliderIcon); + }); + + it('should render READONLY list item and not call onDelete', () => { + let itemView = TestUtils.renderIntoDocument( + <ListEditorItemView onDelete={()=>{}} isReadOnlyMode={true}> + <div></div> + </ListEditorItemView> + ); + expect(itemView).toExist(); + let sliderIcon = TestUtils.scryRenderedDOMComponentsWithClass(itemView, 'fa-trash-o'); + expect(sliderIcon).toEqual(0); + }); +}); diff --git a/openecomp-ui/test/nfvo-components/notifications/notificationsModal.test.js b/openecomp-ui/test/nfvo-components/notifications/notificationsModal.test.js new file mode 100644 index 0000000000..f84d38246d --- /dev/null +++ b/openecomp-ui/test/nfvo-components/notifications/notificationsModal.test.js @@ -0,0 +1,144 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import store from 'sdc-app/AppStore.js'; +import ConnectedNotificationModal, {NotificationModal} from 'nfvo-components/notifications/NotificationModal.jsx'; +import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js'; + +const title = 'test title'; +const msg = 'test msg'; + +describe('Notification Modal Mapper and View Class: ', function () { + + it('notification should show with type error', done => { + store.dispatch({type: NotificationConstants.NOTIFY_ERROR, data: {title, msg}}); + setTimeout(()=> { + expect(store.getState().notification).toExist(); + expect(store.getState().notification.type).toBe('error'); + done(); + }, 0); + }); + + it('notification should show with type default', done => { + store.dispatch({type: NotificationConstants.NOTIFY_INFO, data: {title, msg}}); + setTimeout(()=> { + expect(store.getState().notification).toExist(); + expect(store.getState().notification.type).toBe('default'); + done(); + }, 0); + }); + + it('notification should show with type warning', done => { + store.dispatch({type: NotificationConstants.NOTIFY_WARNING, data: {title, msg}}); + setTimeout(()=> { + expect(store.getState().notification).toExist(); + expect(store.getState().notification.type).toBe('warning'); + done(); + }, 0); + }); + + it('notification should show with type success', done => { + store.dispatch({type: NotificationConstants.NOTIFY_SUCCESS, data: {title, msg}}); + setTimeout(()=> { + expect(store.getState().notification).toExist(); + expect(store.getState().notification.type).toBe('success'); + done(); + }, 0); + }); + + it('notification should show with type success with connected component', done => { + store.dispatch({type: NotificationConstants.NOTIFY_SUCCESS, data: {title, msg}}); + setTimeout(()=> { + expect(store.getState().notification).toExist(); + expect(store.getState().notification.type).toBe('success'); + let renderer = TestUtils.createRenderer(); + renderer.render(<ConnectedNotificationModal store={store}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + done(); + }, 0); + }); + + it('notification should hide with connected component', done => { + setTimeout(()=> { + expect(store.getState().notification).toNotExist(); + let renderer = TestUtils.createRenderer(); + renderer.render(<ConnectedNotificationModal store={store}/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + done(); + }, 0); + store.dispatch({type: NotificationConstants.NOTIFY_CLOSE}); + }); + + it('notification should hide', done => { + store.dispatch({type: NotificationConstants.NOTIFY_CLOSE}); + setTimeout(()=> { + expect(store.getState().notification).toNotExist(); + done(); + }, 0); + }); + + it('NotificationModal should not render', ()=> { + let renderer = TestUtils.createRenderer(); + renderer.render(<NotificationModal show={false} title={title} msg={msg} type='error'/>); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('NotificationModal basic default render', ()=> { + expect(window.document).toExist(); + let document = TestUtils.renderIntoDocument( + <NotificationModal show={true} title={title} msg={msg} type='default' onCloseClick={()=>{}}/> + ); + var result = TestUtils.findAllInRenderedTree(document, element => element.props.className === 'notification-modal primary'); + expect(result.length).toBeGreaterThan(0); + }); + + it('NotificationModal basic error render', ()=> { + expect(window.document).toExist(); + let document = TestUtils.renderIntoDocument( + <NotificationModal show={true} title={title} msg={msg} type='error' onCloseClick={()=>{}}/> + ); + var result = TestUtils.findAllInRenderedTree(document, element => element.props.className === 'notification-modal danger'); + expect(result.length).toBeGreaterThan(0); + }); + + it('NotificationModal basic warning render', ()=> { + expect(window.document).toExist(); + let document = TestUtils.renderIntoDocument( + <NotificationModal show={true} title={title} msg={msg} type='warning' onCloseClick={()=>{}}/> + ); + var result = TestUtils.findAllInRenderedTree(document, element => element.props.className === 'notification-modal warning'); + expect(result.length).toBeGreaterThan(0); + }); + + it('NotificationModal basic success render', ()=> { + expect(window.document).toExist(); + let document = TestUtils.renderIntoDocument( + <NotificationModal show={true} title={title} msg={msg} type='success' onCloseClick={()=>{}}/> + ); + var result = TestUtils.findAllInRenderedTree(document, element => element.props.className === 'notification-modal success'); + expect(result.length).toBeGreaterThan(0); + }); +}); diff --git a/openecomp-ui/test/nfvo-components/panel/VersionController/versionController.test.js b/openecomp-ui/test/nfvo-components/panel/VersionController/versionController.test.js new file mode 100644 index 0000000000..9ab18137cf --- /dev/null +++ b/openecomp-ui/test/nfvo-components/panel/VersionController/versionController.test.js @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import VersionController from 'nfvo-components/panel/versionController/VersionController.jsx'; +import {actionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + +describe('versionController UI Component', () => { + + it('function does exist', () => { + var renderer = TestUtils.createRenderer(); + renderer.render(<VersionController isCheckedOut={false} status={'OUT'} />); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('validating checkin function', () => { + + let versionController = TestUtils.renderIntoDocument(<VersionController isCheckedOut={false} status={'OUT'} onSave={()=>{return Promise.resolve();}}/>); + let cb = action => expect(action).toBe(actionsEnum.CHECK_IN); + versionController.checkin(cb); + + }); + +}); diff --git a/openecomp-ui/test/nfvo-components/panel/VersionController/versionControllerUtils.test.js b/openecomp-ui/test/nfvo-components/panel/VersionController/versionControllerUtils.test.js new file mode 100644 index 0000000000..0e4a92118e --- /dev/null +++ b/openecomp-ui/test/nfvo-components/panel/VersionController/versionControllerUtils.test.js @@ -0,0 +1,172 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import deepFreeze from 'deep-freeze'; +import Configuration from 'sdc-app/config/Configuration.js'; +import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js'; +import {statusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + +const status = 'testStatus'; + +describe('versionController UI Component', () => { + + it('function does exist', () => { + expect(VersionControllerUtils).toExist(); + }); + + it('validating getCheckOutStatusKindByUserID - without "UserID"', () => { + var result = VersionControllerUtils.getCheckOutStatusKindByUserID(status); + expect(result.status).toBe(status); + expect(result.isCheckedOut).toBe(true); + }); + + it('validating getCheckOutStatusKindByUserID - without "UserID" with locking user', () => { + var result = VersionControllerUtils.getCheckOutStatusKindByUserID(status, 'locking user'); + expect(result.status).toBe(statusEnum.LOCK_STATUS); + expect(result.isCheckedOut).toBe(false); + }); + + it('validating getCheckOutStatusKindByUserID - with "UserID" with configuration set', () => { + const userId = 'att'; + + Configuration.set('ATTUserID', userId); + var result = VersionControllerUtils.getCheckOutStatusKindByUserID(status, userId); + Configuration.set('ATTUserID', undefined); + + expect(result.status).toBe(status); + expect(result.isCheckedOut).toBe(true); + }); + + + + it('validating isCheckedOutByCurrentUser - when resource is not checked out', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Final' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isCheckedOutByCurrentUser(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(false); + }); + + it('validating isCheckedOutByCurrentUser - when resource is checked out', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Locked', + lockingUser: 'current' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isCheckedOutByCurrentUser(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(true); + }); + + it('validating isCheckedOutByCurrentUser - when resource is checked out by another user', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Locked', + lockingUser: 'another' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isCheckedOutByCurrentUser(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(false); + }); + + + + it('validating isReadOnly - when resource is not checked out', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Final' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isReadOnly(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(true); + }); + + it('validating isReadOnly - when resource is checked out', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Locked', + lockingUser: 'current' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isReadOnly(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(false); + }); + + it('validating isReadOnly - when version of resource is not latest', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.2', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Locked', + lockingUser: 'current' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isReadOnly(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(true); + }); + + it('validating isReadOnly - when resource is checked out by another user', () => { + const currentUser = 'current'; + const resource = deepFreeze({ + version: '0.6', + viewableVersions: ['0.1', '0.2', '0.3', '0.4', '0.5', '0.6'], + status: 'Locked', + lockingUser: 'another' + }); + + Configuration.set('ATTUserID', currentUser); + const result = VersionControllerUtils.isReadOnly(resource); + Configuration.set('ATTUserID', undefined); + + expect(result).toBe(true); + }); +}); + diff --git a/openecomp-ui/test/setup.test.js b/openecomp-ui/test/setup.test.js new file mode 100644 index 0000000000..72f8b954b8 --- /dev/null +++ b/openecomp-ui/test/setup.test.js @@ -0,0 +1,25 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import mockRest from 'test-utils/MockRest.js'; + +beforeEach(function() { + mockRest.resetQueue(); +}); diff --git a/openecomp-ui/test/softwareProduct/attachments/SoftwareProductAttachmentsView.test.js b/openecomp-ui/test/softwareProduct/attachments/SoftwareProductAttachmentsView.test.js new file mode 100644 index 0000000000..839176c970 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/attachments/SoftwareProductAttachmentsView.test.js @@ -0,0 +1,198 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js'; + +import SoftwareProductAttachmentsView from 'sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + + +describe('SoftwareProductAttachments Modal Mapper and View Classes', () => { + + it ('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + + it ('mapStateToProps check data', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + const atTree = { + 'children': [ + { + 'name': 'HEAT', + 'expanded': true, + 'type': 'heat', + 'children': [ + { + 'name': 'heat_zxeyCtMHhf2.yaml', + 'expanded': true, + 'type': 'heat', + 'errors': [ + { + 'level': 'WARNING', + 'message': 'Resource is not defined as output and thus cannot be Shared. resource id - network_4' + } + ], + 'children': [ + { + 'name': 'heat_env_zxeyCtMHhf2.env', + 'type': 'env' + } + ] + } + ] + } + ] + }; + const errorList = [ + { + 'errorLevel': 'WARNING', + 'errorMessage': 'Resource is not defined as output and thus cannot be Shared. resource id - network_4', + 'name': 'heat_zxeyCtMHhf2.yaml', + 'hasParent': false, + 'parentName': 'HEAT', + 'type': 'heat' + }, + { + 'errorLevel': 'WARNING', + 'errorMessage': 'Resource is not defined as output and thus cannot be Shared. resource id - network_3', + 'name': 'heat_zxeyCtMHhf2.yaml', + 'hasParent': false, + 'parentName': 'HEAT', + 'type': 'heat' + } + ]; + + var obj = { + softwareProduct: { + softwareProductEditor: { + data:currentSoftwareProduct + }, softwareProductAttachments: + { + attachmentsTree: atTree, + errorList: errorList + } + } + }; + + var results = mapStateToProps(obj); + expect(results.attachmentsTree).toExist(); + expect(results.errorList).toExist(); + expect(results.hoveredNode).toBe(undefined); + expect(results.selectedNode).toBe(undefined); + }); + + + it('function does exist', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + const versionControllerData = { + version: currentSoftwareProduct.version, + viewableVersions:currentSoftwareProduct.viewableVersions, + status: currentSoftwareProduct.status, + isCheckedOut: true + }; + const atTree = { + 'children': [ + { + 'name': 'HEAT', + 'expanded': true, + 'type': 'heat', + 'children': [ + { + 'name': 'heat_zxeyCtMHhf2.yaml', + 'expanded': true, + 'type': 'heat', + 'errors': [ + { + 'level': 'WARNING', + 'message': 'Resource is not defined as output and thus cannot be Shared. resource id - network_4' + } + ], + 'children': [ + { + 'name': 'heat_env_zxeyCtMHhf2.env', + 'type': 'env' + } + ] + } + ] + } + ] + }; + const errorList = [ + { + 'errorLevel': 'WARNING', + 'errorMessage': 'Resource is not defined as output and thus cannot be Shared. resource id - network_4', + 'name': 'heat_zxeyCtMHhf2.yaml', + 'hasParent': false, + 'parentName': 'HEAT', + 'type': 'heat' + }, + { + 'errorLevel': 'WARNING', + 'errorMessage': 'Resource is not defined as output and thus cannot be Shared. resource id - network_3', + 'name': 'heat_zxeyCtMHhf2.yaml', + 'hasParent': false, + 'parentName': 'HEAT', + 'type': 'heat' + } + ]; + + + var renderer = TestUtils.createRenderer(); + renderer.render(<SoftwareProductAttachmentsView + versionControllerData={versionControllerData} + currentSoftwareProduct={currentSoftwareProduct} + attachmentsTree={atTree} + errorList={errorList}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + +}); diff --git a/openecomp-ui/test/softwareProduct/attachments/SoftwareproductAttachmentsHelper.test.js b/openecomp-ui/test/softwareProduct/attachments/SoftwareproductAttachmentsHelper.test.js new file mode 100644 index 0000000000..851560caa8 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/attachments/SoftwareproductAttachmentsHelper.test.js @@ -0,0 +1,153 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import SoftwareProductAttachmentsActionHelper from 'sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import deepFreeze from 'deep-freeze'; +import {actionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; + + + + + + +describe('SoftwareProductAttachments ActionHelper', () => { + + it('function does exist', () => { + expect(SoftwareProductAttachmentsActionHelper).toExist(); + }); + + it('toggleExpanded function check', () => { + + + const validationData = { + importStructure: { + HEAT: [ + { + fileName: 'hot-mog-0108-bs1271.yml', + env: { + fileName: 'hot-mog-0108-bs1271.env' + }, + errors: [ + { + 'level': 'WARNING', + 'message': 'Port not bind to any NOVA Server, Resource Id [sm02_port_2]' + }, + { + 'level': 'WARNING', + 'message': 'Port not bind to any NOVA Server, Resource Id [sm01_port_2]' + } + ] + } + ] + } + }; + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: 'Locked', + lockingUser: 'cs0008', + validationData + }; + + + const store = storeCreator(); + deepFreeze(store.getState()); + deepFreeze(currentSoftwareProduct); + + store.dispatch({ + type:actionTypes.SOFTWARE_PRODUCT_LOADED, + response: currentSoftwareProduct + }); + + expect(store.getState().softwareProduct.softwareProductAttachments.attachmentsTree.children[0].expanded).toBe(true); + SoftwareProductAttachmentsActionHelper.toggleExpanded(store.dispatch, {path:[0]}); + expect(store.getState().softwareProduct.softwareProductAttachments.attachmentsTree.children[0].expanded).toBe(false); + }); + + it('onSelectNode & onUnselectNode function check', () => { + + + const validationData = { + importStructure: { + HEAT: [ + { + fileName: 'hot-mog-0108-bs1271.yml', + env: { + fileName: 'hot-mog-0108-bs1271.env' + }, + errors: [ + { + 'level': 'WARNING', + 'message': 'Port not bind to any NOVA Server, Resource Id [sm02_port_2]' + }, + { + 'level': 'WARNING', + 'message': 'Port not bind to any NOVA Server, Resource Id [sm01_port_2]' + } + ] + } + ] + } + }; + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: 'Locked', + lockingUser: 'cs0008', + validationData + }; + + deepFreeze(currentSoftwareProduct); + + const store = storeCreator(); + deepFreeze(store.getState()); + + store.dispatch({ + type:actionTypes.SOFTWARE_PRODUCT_LOADED, + response: currentSoftwareProduct + }); + let expectedNodeName = 'name'; + expect(store.getState().softwareProduct.softwareProductAttachments.selectedNode).toBe(undefined); + SoftwareProductAttachmentsActionHelper.onSelectNode(store.dispatch, {nodeName:expectedNodeName}); + expect(store.getState().softwareProduct.softwareProductAttachments.selectedNode).toBe(expectedNodeName); + SoftwareProductAttachmentsActionHelper.onUnselectNode(store.dispatch); + expect(store.getState().softwareProduct.softwareProductAttachments.selectedNode).toBe(undefined); + }); + + +}); diff --git a/openecomp-ui/test/softwareProduct/components/compute/test.js b/openecomp-ui/test/softwareProduct/components/compute/test.js new file mode 100644 index 0000000000..925de302b8 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/compute/test.js @@ -0,0 +1,132 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; + +const softwareProductId = '123'; +const vspComponentId = '111'; + +describe('Software Product Components Compute Module Tests', function () { + + let restPrefix = ''; + + before(function() { + restPrefix = Configuration.get('restPrefix'); + deepFreeze(restPrefix); + }); + + it('Get Software Products Components Compute', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductComponentCompute = { + data: JSON.stringify({'vmSizing':{'numOfCPUs':'3','fileSystemSizeGB':'888'},'numOfVMs':{'minimum':'2'}}), + schema: JSON.stringify({'vmSizing':{'numOfCPUs':'3','fileSystemSizeGB':'888'},'numOfVMs':{'minimum':'2'}}) + }; + deepFreeze(softwareProductComponentCompute); + + const softwareProductComponentComputeData = { + qdata: JSON.parse(softwareProductComponentCompute.data), + qschema: JSON.parse(softwareProductComponentCompute.schema) + }; + deepFreeze(softwareProductComponentComputeData); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor', softwareProductComponentComputeData); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).toEqual(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${vspComponentId}/questionnaire`); + expect(data).toEqual(undefined); + expect(options).toEqual(undefined); + return softwareProductComponentCompute; + }); + + return SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(store.dispatch, {softwareProductId, vspComponentId}).then(() => { + expect(store.getState()).toEqual(expectedStore); + }); + }); + + it('Get Empty Software Products Components Compute', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductComponentQuestionnaire = { + data: null, + schema: JSON.stringify({'vmSizing':{'numOfCPUs':'3','fileSystemSizeGB':'888'},'numOfVMs':{'minimum':'2'}}) + }; + deepFreeze(softwareProductComponentQuestionnaire); + + const softwareProductComponentQuestionnaireData = { + qdata: {}, + qschema: JSON.parse(softwareProductComponentQuestionnaire.schema) + }; + deepFreeze(softwareProductComponentQuestionnaireData); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor', softwareProductComponentQuestionnaireData); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).toEqual(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${vspComponentId}/questionnaire`); + expect(data).toEqual(undefined); + expect(options).toEqual(undefined); + return softwareProductComponentQuestionnaire; + }); + + return SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(store.dispatch, {softwareProductId, vspComponentId}).then(() => { + expect(store.getState()).toEqual(expectedStore); + }); + }); + + it('Update Software Products Components Compute', () => { + const store = storeCreator({ + softwareProduct: { + softwareProductComponents: { + componentEditor: { + qdata: { + numOfCPUs: 3, + fileSystemSizeGB: 999 + }, + qschema: { + type: 'object', + properties: { + numOfCPUs: {type: 'number'}, + fileSystemSizeGB: {type: 'number'} + } + } + } + } + } + }); + deepFreeze(store); + + const data = {numOfCPUs: 5, fileSystemSizeGB: 300}; + deepFreeze(data); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor.qdata', data); + + SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(store.dispatch, {data}); + + expect(store.getState()).toEqual(expectedStore); + }); +}); diff --git a/openecomp-ui/test/softwareProduct/components/general/SoftwareProductComponentsGeneral.test.js b/openecomp-ui/test/softwareProduct/components/general/SoftwareProductComponentsGeneral.test.js new file mode 100644 index 0000000000..ce2152b29b --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/general/SoftwareProductComponentsGeneral.test.js @@ -0,0 +1,129 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js'; +import SoftwareProductComponentsGeneralView from 'sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + + +describe('SoftwareProductComponentsGeneral Mapper and View Classes', () => { + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it('mapStateToProps data test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + var obj = { + softwareProduct: { + softwareProductEditor: { + data: currentSoftwareProduct + }, + softwareProductComponents: { + componentEditor: { + data: {}, + qdata: {}, + qschema: {} + } + } + } + }; + + var results = mapStateToProps(obj); + expect(results.componentData).toExist(); + expect(results.qdata).toExist(); + expect(results.qschema).toExist(); + }); + + + it('VSP Components general view test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + const softwareProductComponents = { + componentEditor: { + data: {}, + qdata: {}, + qschema: { + $schema: 'http://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + general: { + type: 'object', + properties: {} + } + } + } + } + }; + + const versionControllerData = { + version: '1', + viewableVersions: [], + status: 'locked', + isCheckedOut: true + }; + + const componentData = { + name: '', + description: '' + }; + + var renderer = TestUtils.createRenderer(); + renderer.render( + <SoftwareProductComponentsGeneralView + componentData={componentData} + softwareProductComponents={softwareProductComponents} + versionControllerData={versionControllerData} + currentSoftwareProduct={currentSoftwareProduct}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + + }); + +}); diff --git a/openecomp-ui/test/softwareProduct/components/loadBalancing/softwareProductComponentLoadbalancing.test.js b/openecomp-ui/test/softwareProduct/components/loadBalancing/softwareProductComponentLoadbalancing.test.js new file mode 100644 index 0000000000..69a93b69e1 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/loadBalancing/softwareProductComponentLoadbalancing.test.js @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js'; +import SoftwareProductComponentLoadBalancingView from 'sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + + +describe('SoftwareProductComponentLoadBalancing Mapper and View Classes', () => { + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it('mapStateToProps data test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + var obj = { + softwareProduct: { + softwareProductEditor: { + data: currentSoftwareProduct + }, + softwareProductComponents: { + componentEditor: { + qdata: {}, + qschema: {} + } + } + } + }; + + var results = mapStateToProps(obj); + expect(results.qdata).toExist(); + expect(results.qschema).toExist(); + }); + + + it('VSP Components LoadBalancing view test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + const softwareProductComponents = { + componentEditor: { + qdata: {}, + qschema: { + $schema: 'http://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + general: { + type: 'object', + properties: {} + } + } + } + } + }; + + const versionControllerData = { + version: '1', + viewableVersions: [], + status: 'locked', + isCheckedOut: true + }; + + var renderer = TestUtils.createRenderer(); + renderer.render( + <SoftwareProductComponentLoadBalancingView + softwareProductComponents={softwareProductComponents} + versionControllerData={versionControllerData} + currentSoftwareProduct={currentSoftwareProduct} + softwareProductId='123' + componentId='321'/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + + }); + +}); diff --git a/openecomp-ui/test/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.test.js b/openecomp-ui/test/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.test.js new file mode 100644 index 0000000000..2f1ea12c01 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.test.js @@ -0,0 +1,101 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js'; +import SoftwareProductComponentsMonitoringView from 'sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx'; + +describe('SoftwareProductComponentsMonitoring Module Tests', function () { + + it('should mapper exist', () => { + expect(mapStateToProps).toExist(); + }); + + it('should return empty file names', () => { + let softwareProduct = {softwareProductEditor: {data: {}}, softwareProductComponents: {monitoring: {}}}; + var results = mapStateToProps({softwareProduct}); + expect(results.trapFilename).toEqual(undefined); + expect(results.pollFilename).toEqual(undefined); + }); + + it('should return trap file name', () => { + const monitoring = { + trapFilename: '123' + }; + let softwareProduct = {softwareProductEditor: {data: {}}, softwareProductComponents: {monitoring}}; + var results = mapStateToProps({softwareProduct}); + expect(results.trapFilename).toEqual(monitoring.trapFilename); + expect(results.pollFilename).toEqual(undefined); + }); + + it('should return poll file names', () => { + const monitoring = { + pollFilename: '123' + }; + let softwareProduct = {softwareProductEditor: {data: {}}, softwareProductComponents: {monitoring}}; + var results = mapStateToProps({softwareProduct}); + expect(results.trapFilename).toEqual(undefined); + expect(results.pollFilename).toEqual(monitoring.pollFilename); + + let renderer = TestUtils.createRenderer(); + renderer.render(<SoftwareProductComponentsMonitoringView {...results} />); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('should return both file names', () => { + const monitoring = { + trapFilename: '1234', + trapFilename: '123' + }; + let softwareProduct = {softwareProductEditor: {data: {}}, softwareProductComponents: {monitoring}}; + var results = mapStateToProps({softwareProduct}); + expect(results.trapFilename).toEqual(monitoring.trapFilename); + expect(results.pollFilename).toEqual(monitoring.pollFilename); + + let renderer = TestUtils.createRenderer(); + renderer.render(<SoftwareProductComponentsMonitoringView {...results} />); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('should change state to dragging', done => { + var view = TestUtils.renderIntoDocument(<SoftwareProductComponentsMonitoringView />); + expect(view.state.dragging).toBe(false); + view.handleOnDragEnter(false); + setTimeout(()=> { + expect(view.state.dragging).toBe(true); + done(); + }, 100); + }); + + it('should not change state to dragging', done => { + var view = TestUtils.renderIntoDocument(<SoftwareProductComponentsMonitoringView />); + expect(view.state.dragging).toBe(false); + view.handleOnDragEnter(true); + setTimeout(()=> { + expect(view.state.dragging).toBe(false); + done(); + }, 0); + }); + +}); diff --git a/openecomp-ui/test/softwareProduct/components/monitoring/test.js b/openecomp-ui/test/softwareProduct/components/monitoring/test.js new file mode 100644 index 0000000000..172db653e9 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/monitoring/test.js @@ -0,0 +1,215 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import mockRest from 'test-utils/MockRest.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import SoftwareProductComponentsMonitoringConstants from 'sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js'; +import SoftwareProductComponentsMonitoringActionHelper from 'sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js'; + +const softwareProductId = '123'; +const componentId = '123'; + +describe('Software Product Components Monitoring Module Tests', function () { + + let store; + + beforeEach(()=> { + store = storeCreator(); + }); + + + it('Fetch for existing files - no files', done => { + + let emptyResult = Object.freeze({ + snmpTrap: undefined, + snmpPoll: undefined + }); + + mockRest.addHandler('fetch', ({ baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp`); + return emptyResult; + }); + + SoftwareProductComponentsMonitoringActionHelper.fetchExistingFiles(store.dispatch, { + softwareProductId, + componentId + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(emptyResult.snmpPoll); + expect(monitoring.trapFilename).toEqual(emptyResult.snmpTrap); + done(); + }, 0); + + }); + + it('Fetch for existing files - only snmp trap file exists', done => { + let response = Object.freeze({ + snmpTrap: 'asdfasdf', + snmpPoll: undefined + }); + + mockRest.addHandler('fetch', ({ baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp`); + return response; + }); + + SoftwareProductComponentsMonitoringActionHelper.fetchExistingFiles(store.dispatch, { + softwareProductId, + componentId + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(response.snmpPoll); + expect(monitoring.trapFilename).toEqual(response.snmpTrap); + done(); + }, 0); + }); + + it('Fetch for existing files - only snmp poll file exists', done => { + let response = Object.freeze({ + snmpPoll: 'asdfasdf', + snmpTrap: undefined + }); + + mockRest.addHandler('fetch', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp`); + return response; + }); + + SoftwareProductComponentsMonitoringActionHelper.fetchExistingFiles(store.dispatch, { + softwareProductId, + componentId + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(response.snmpPoll); + expect(monitoring.trapFilename).toEqual(response.snmpTrap); + done(); + }, 0); + }); + + it('Fetch for existing files - both files exist', done => { + let response = Object.freeze({ + snmpPoll: 'asdfasdf', + snmpTrap: 'asdfgg' + }); + + mockRest.addHandler('fetch', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp`); + return response; + }); + + SoftwareProductComponentsMonitoringActionHelper.fetchExistingFiles(store.dispatch, { + softwareProductId, + componentId + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(response.snmpPoll); + expect(monitoring.trapFilename).toEqual(response.snmpTrap); + done(); + }, 0); + }); + + it('Upload snmp trap file', done => { + + mockRest.addHandler('create', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp-trap/upload`); + return {}; + }); + var debug = {hello: 'world'}; + let file = new Blob([JSON.stringify(debug, null, 2)], {type: 'application/json'});; + let formData = new FormData(); + formData.append('upload', file); + SoftwareProductComponentsMonitoringActionHelper.uploadSnmpFile(store.dispatch, { + softwareProductId, + componentId, + formData, + fileSize: file.size, + type: SoftwareProductComponentsMonitoringConstants.SNMP_TRAP + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(undefined); + expect(monitoring.trapFilename).toEqual('blob'); + done(); + }, 0); + }); + + it('Upload snmp poll file', done => { + mockRest.addHandler('create', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp/upload`); + return {}; + }); + var debug = {hello: 'world'}; + let file = new Blob([JSON.stringify(debug, null, 2)], {type: 'application/json'});; + let formData = new FormData(); + formData.append('upload', file); + SoftwareProductComponentsMonitoringActionHelper.uploadSnmpFile(store.dispatch, { + softwareProductId, + componentId, + formData, + fileSize: file.size, + type: SoftwareProductComponentsMonitoringConstants.SNMP_POLL + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual('blob'); + expect(monitoring.trapFilename).toEqual(undefined); + done(); + }, 0); + }); + + it('Delete snmp trap file', done => { + mockRest.addHandler('destroy', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp-trap`); + return {}; + }); + SoftwareProductComponentsMonitoringActionHelper.deleteSnmpFile(store.dispatch, { + softwareProductId, + componentId, + type: SoftwareProductComponentsMonitoringConstants.SNMP_TRAP + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.trapFilename).toEqual(undefined); + done(); + }, 0); + }); + + it('Delete snmp poll file', done => { + mockRest.addHandler('destroy', ({baseUrl}) => { + expect(baseUrl).toEqual(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/monitors/snmp`); + return {}; + }); + SoftwareProductComponentsMonitoringActionHelper.deleteSnmpFile(store.dispatch, { + softwareProductId, + componentId, + type: SoftwareProductComponentsMonitoringConstants.SNMP_POLL + }); + setTimeout(()=> { + var {softwareProduct: {softwareProductComponents: {monitoring}}} = store.getState(); + expect(monitoring.pollFilename).toEqual(undefined); + done(); + }, 0); + }); +}); diff --git a/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNICEditor.test.js b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNICEditor.test.js new file mode 100644 index 0000000000..c9760f7799 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNICEditor.test.js @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js'; +import SoftwareProductComponentsNICEditorView from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx'; + + + +describe('Software Product Component Network NIC Editor and View Classes', () => { + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + + it('mapStateToProps data test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + lockingUser: 'cs0008' + }; + + + var obj = { + softwareProduct: { + softwareProductEditor: { + data: currentSoftwareProduct + }, + softwareProductComponents: { + network: { + nicEditor: { + data: {}, + qdata: {}, + qschema: {} + } + } + } + } + }; + + var results = mapStateToProps(obj); + expect(results.currentSoftwareProduct).toExist(); + expect(results.qdata).toExist(); + expect(results.qschema).toExist(); + expect(results.data).toExist(); + }); + + + it('Software Product Component Network NIC Editor View Test', () => { + + const data = { + name: '', + description: '', + networkName: '' + }; + + const qdata = {}; + const qschema = {}; + + var renderer = TestUtils.createRenderer(); + renderer.render( + <SoftwareProductComponentsNICEditorView + data={data} + qdata={qdata} + qschema={qschema}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + + }); +}); diff --git a/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetwork.test.js b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetwork.test.js new file mode 100644 index 0000000000..520fde7403 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetwork.test.js @@ -0,0 +1,125 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js'; +import SoftwareProductComponentsNetworkListView from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + + +describe('Software Product Component Network Mapper and View Classes', () => { + + it('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it('mapStateToProps data test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + + var obj = { + softwareProduct: { + softwareProductEditor: { + data: currentSoftwareProduct + }, + softwareProductComponents: { + componentEditor: { + qdata: {}, + qschema: {}, + data: {} + }, + network: { + nicEditor: {}, + nicList: [] + } + } + } + }; + + var results = mapStateToProps(obj); + expect(results.qdata).toExist(); + expect(results.qschema).toExist(); + expect(results.componentData).toExist(); + }); + + it('Software Product Component Network List View Test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_IN_STATUS, + lockingUser: 'cs0008' + }; + + const versionControllerData = { + version: '1', + viewableVersions: [], + status: 'locked', + isCheckedOut: true + }; + + const nicList = [ + { + name: 'name', + networkId: 'network', + id: '122', + networkName: 'nname' + } + ]; + + var renderer = TestUtils.createRenderer(); + renderer.render( + <SoftwareProductComponentsNetworkListView + versionControllerData={versionControllerData} + currentSoftwareProduct={currentSoftwareProduct} + softwareProductId='123' + componentId='321' + nicList={nicList}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + + + + + }); + +}); diff --git a/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.test.js b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.test.js new file mode 100644 index 0000000000..8c23267c89 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.test.js @@ -0,0 +1,305 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import SoftwareProductComponentsNetworkActionHelper from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js'; + +const softwareProductId = '123'; +const componentId = '321'; +const nicId = '111'; + +describe('Software Product Components Network Action Helper Tests', function () { + + it('Fetch NICs List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const NICList = [ + { + name:'oam01_port_0', + description:'bbbbbbb', + networkId:'A0E578751B284D518ED764D5378EA97C', + id:'96D3648338F94DAA9889E9FBB8E59895', + networkName:'csb_net' + }, + { + name:'oam01_port_1', + description:'bbbbbbb', + networkId:'378EA97CA0E578751B284D518ED764D5', + id:'8E5989596D3648338F94DAA9889E9FBB', + networkName:'csb_net_2' + } + + ]; + + deepFreeze(NICList); + + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network.nicList', NICList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: NICList}; + }); + + return SoftwareProductComponentsNetworkActionHelper.fetchNICsList(store.dispatch, {softwareProductId, componentId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + + }); + + it('open NICE editor', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + const data = { + name: 'oam01_port_0', + description: 'bbbbbbb', + networkId: 'A0E578751B284D518ED764D5378EA97C', + networkName: 'csb_net' + }; + + const nic = {id: '444'}; + deepFreeze(data); + deepFreeze(nic); + + const expectedData = {...data, id: nic.id}; + + deepFreeze(expectedData); + + const network = { + nicEditor: { + data: expectedData + }, + nicList: [] + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + + SoftwareProductComponentsNetworkActionHelper.openNICEditor(store.dispatch, {nic, data}); + + return setTimeout(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }, 100); + }); + + it('close NICE editor', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const network = { + nicEditor: {}, + nicList: [] + }; + deepFreeze(network); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + + SoftwareProductComponentsNetworkActionHelper.closeNICEditor(store.dispatch); + + return setTimeout(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }, 100); + }); + + it('Load NIC data', () => { + + const expectedData = { + description: 'bbbbbbb', + name: 'oam01_port_0', + networkId: 'A0E578751B284D518ED764D5378EA97C', + networkName: 'csb_net' + }; + + deepFreeze(expectedData); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics/${nicId}`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return (expectedData); + }); + + return SoftwareProductComponentsNetworkActionHelper.loadNICData({softwareProductId, componentId, nicId}).then((data) => { + expect(data).to.deep.equal(expectedData); + }); + }); + + + it('load NIC Questionnaire', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const qdata = { + protocols: { + protocolWithHighestTrafficProfile: 'UDP', + protocols: ['UDP'] + }, + ipConfiguration: { + ipv4Required: true + } + }; + + const qschema = { + $schema: 'http://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + 'protocols': { + type: 'object', + properties: {} + } + } + }; + + deepFreeze(qdata); + deepFreeze(qschema); + + + const network = { + nicEditor: { + qdata, + qschema + }, + nicList: [] + }; + deepFreeze(network); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics/${nicId}/questionnaire`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return ({data: JSON.stringify(qdata), schema: JSON.stringify(qschema)}); + }); + + return SoftwareProductComponentsNetworkActionHelper.loadNICQuestionnaire(store.dispatch, {softwareProductId, componentId, nicId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('update NIC Data', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const data = {test: '123'}; + deepFreeze(data); + + const network = { + nicEditor: { + data + }, + nicList: [] + }; + + deepFreeze(network); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + + SoftwareProductComponentsNetworkActionHelper.updateNICData(store.dispatch, {deltaData:data}); + + return setTimeout(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }, 100); + + }); + + it('update NIC Questionnaire', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const qdata = { + test: '123' + }; + const network = { + nicEditor: { + qdata, + qschema: undefined + }, + nicList: [] + }; + deepFreeze(network); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + + SoftwareProductComponentsNetworkActionHelper.updateNICQuestionnaire(store.dispatch, {data:qdata}); + + return setTimeout(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }, 100); + + }); + + it('save NIC Data And Questionnaire', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const qdata = { + qtest: '111' + }; + const data = { + name: '2222', + description: 'blabla', + networkId: '123445' + }; + + const expectedData = {...data, id: nicId}; + + const network = { + nicEditor: {}, + nicList: [ + expectedData + ] + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.network', network); + deepFreeze(expectedStore); + + mockRest.addHandler('save', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics/${nicId}/questionnaire`); + expect(data).to.deep.equal(qdata); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + mockRest.addHandler('save', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics/${nicId}`); + expect(data).to.deep.equal(data); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductComponentsNetworkActionHelper.saveNICDataAndQuestionnaire(store.dispatch, {softwareProductId, componentId, qdata, data: expectedData}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + +}); diff --git a/openecomp-ui/test/softwareProduct/components/processes/test.js b/openecomp-ui/test/softwareProduct/components/processes/test.js new file mode 100644 index 0000000000..67427d3c05 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/processes/test.js @@ -0,0 +1,214 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import SoftwareProductComponentProcessesActionHelper from 'sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js'; + +const softwareProductId = '123'; +const componentId = '222'; +describe('Software Product Component Processes Module Tests', function () { + it('Get Software Products Processes List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'hjhj', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + }, + { + name: 'Pr1', + description: 'hjhj', + id: '2F47447D22DB4C53B020CA1E66201EF2', + artifactName: 'artifact' + } + ]; + + deepFreeze(softwareProductProcessesList); + + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentProcesses.processesList', softwareProductProcessesList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: softwareProductProcessesList}; + }); + + return SoftwareProductComponentProcessesActionHelper.fetchProcessesList(store.dispatch, {softwareProductId, componentId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + it('Delete Software Products Processes', () => { + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'hjhj', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + } + ]; + + deepFreeze(softwareProductProcessesList); + const store = storeCreator({ + softwareProduct: { + softwareProductProcesses: { + processesList: softwareProductProcessesList + } + } + }); + + const processId = 'EBADF561B7FA4A788075E1840D0B5971'; + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentProcesses.processesList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes/${processId}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return { + results: { + returnCode: 'OK' + } + }; + }); + + return SoftwareProductComponentProcessesActionHelper.deleteProcess(store.dispatch, { + process: softwareProductProcessesList[0], + softwareProductId, componentId + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add Software Products Processes', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductPostRequest = { + name: 'Pr1', + description: 'string' + }; + const softwareProductProcessToAdd = { + name: 'Pr1', + description: 'string' + }; + const softwareProductProcessFromResponse = 'ADDED_ID'; + const softwareProductProcessAfterAdd = { + ...softwareProductProcessToAdd, + id: softwareProductProcessFromResponse + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentProcesses.processesList', [softwareProductProcessAfterAdd]); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes`); + expect(data).to.deep.equal(softwareProductPostRequest); + expect(options).to.equal(undefined); + return { + returnCode: 'OK', + value: softwareProductProcessFromResponse + }; + }); + + return SoftwareProductComponentProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId, + previousProcess: null, + process: softwareProductProcessToAdd, + componentId + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update Software Products Processes', () => { + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'string', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + } + ]; + deepFreeze(softwareProductProcessesList); + + const store = storeCreator({ + softwareProduct: { + softwareProductComponents: { + componentProcesses: { + processesList: softwareProductProcessesList + } + } + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedProcessId = softwareProductProcessesList[0].id; + const previousProcessData = softwareProductProcessesList[0]; + const processUpdateData = { + ...softwareProductProcessesList[0], + name: 'Pr1_UPDATED', + description: 'string_UPDATED' + }; + deepFreeze(processUpdateData); + + const processPutRequest = { + name: 'Pr1_UPDATED', + description: 'string_UPDATED' + }; + deepFreeze(processPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentProcesses.processesList', [processUpdateData]); + + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes/${toBeUpdatedProcessId}`); + expect(data).to.deep.equal(processPutRequest); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductComponentProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId: softwareProductId, + componentId, + previousProcess: previousProcessData, + process: processUpdateData + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + +}); + diff --git a/openecomp-ui/test/softwareProduct/components/storage/test.js b/openecomp-ui/test/softwareProduct/components/storage/test.js new file mode 100644 index 0000000000..87cad368be --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/storage/test.js @@ -0,0 +1,132 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; + +const softwareProductId = '123'; +const vspComponentId = '111'; + +describe('Software Product Components Storage Module Tests', function () { + + let restPrefix = ''; + + before(function() { + restPrefix = Configuration.get('restPrefix'); + deepFreeze(restPrefix); + }); + + it('Get Software Products Components Storage', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductComponentStorage = { + data: JSON.stringify({'backup':{'backupType':'OnSite','backupSolution':'76333'},'snapshotBackup':{'snapshotFrequency':'2'}}), + schema: JSON.stringify({'backup':{'backupType':'OnSite','backupSolution':'76333'},'snapshotBackup':{'snapshotFrequency':'2'}}) + }; + deepFreeze(softwareProductComponentStorage); + + const softwareProductComponentStorageData = { + qdata: JSON.parse(softwareProductComponentStorage.data), + qschema: JSON.parse(softwareProductComponentStorage.schema) + }; + deepFreeze(softwareProductComponentStorageData); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor', softwareProductComponentStorageData); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).toEqual(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${vspComponentId}/questionnaire`); + expect(data).toEqual(undefined); + expect(options).toEqual(undefined); + return softwareProductComponentStorage; + }); + + return SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(store.dispatch, {softwareProductId, vspComponentId}).then(() => { + expect(store.getState()).toEqual(expectedStore); + }); + }); + + it('Get Empty Software Products Components Storage', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductComponentQuestionnaire = { + data: null, + schema: JSON.stringify({'backup':{'backupType':'OnSite','backupSolution':'76333'},'snapshotBackup':{'snapshotFrequency':'2'}}) + }; + deepFreeze(softwareProductComponentQuestionnaire); + + const softwareProductComponentQuestionnaireData = { + qdata: {}, + qschema: JSON.parse(softwareProductComponentQuestionnaire.schema) + }; + deepFreeze(softwareProductComponentQuestionnaireData); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor', softwareProductComponentQuestionnaireData); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).toEqual(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${vspComponentId}/questionnaire`); + expect(data).toEqual(undefined); + expect(options).toEqual(undefined); + return softwareProductComponentQuestionnaire; + }); + + return SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(store.dispatch, {softwareProductId, vspComponentId}).then(() => { + expect(store.getState()).toEqual(expectedStore); + }); + }); + + it('Update Software Products Components Storage', () => { + const store = storeCreator({ + softwareProduct: { + softwareProductComponents: { + componentEditor: { + qdata: { + backupType: 'OnSite', + backupStorageSize: 30 + }, + qschema: { + type: 'object', + properties: { + backupType: {type: 'string'}, + backupStorageSize: {type: 'number'} + } + } + } + } + } + }); + deepFreeze(store); + + const data = {backupType: 'OffSite', backupStorageSize: 30}; + deepFreeze(data); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor.qdata', data); + + SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(store.dispatch, {data}); + + expect(store.getState()).toEqual(expectedStore); + }); +}); diff --git a/openecomp-ui/test/softwareProduct/components/test.js b/openecomp-ui/test/softwareProduct/components/test.js new file mode 100644 index 0000000000..839e1b7cf7 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/components/test.js @@ -0,0 +1,101 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js'; + +const softwareProductId = '123'; +const vspComponentId = '321'; + +describe('Software Product Components Module Tests', function () { + it('Get Software Products Components List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductComponentsList = [ + { + name: 'com.d2.resource.vfc.nodes.heat.sm_server', + displayName: 'sm_server', + description: 'hjhj', + id: 'EBADF561B7FA4A788075E1840D0B5971' + }, + { + name: 'com.d2.resource.vfc.nodes.heat.pd_server', + displayName: 'pd_server', + description: 'hjhj', + id: '2F47447D22DB4C53B020CA1E66201EF2' + } + ]; + + deepFreeze(softwareProductComponentsList); + + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentsList', softwareProductComponentsList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: softwareProductComponentsList}; + }); + + return SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(store.dispatch, {softwareProductId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update SoftwareProduct Component Questionnaire', () => { + const store = storeCreator(); + + const qdataUpdated = { + general: { + hypervisor: { + containerFeatureDescription: 'aaaUpdated', + drivers: 'bbbUpdated', + hypervisor: 'cccUpdated' + } + } + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductComponents.componentEditor.qdata', qdataUpdated); + deepFreeze(expectedStore); + + + mockRest.addHandler('save', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/components/${vspComponentId}/questionnaire`); + expect(data).to.deep.equal(qdataUpdated); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(store.dispatch, {softwareProductId, vspComponentId, qdata: qdataUpdated}).then(() => { + //TODO think should we add here something or not + }); + + + }); + +}); + diff --git a/openecomp-ui/test/softwareProduct/details/detailsView.test.js b/openecomp-ui/test/softwareProduct/details/detailsView.test.js new file mode 100644 index 0000000000..b6a8ca5d4e --- /dev/null +++ b/openecomp-ui/test/softwareProduct/details/detailsView.test.js @@ -0,0 +1,438 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js'; +import SoftwareProductDetailsView from 'sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx'; +import {vspQschema as vspQuestionnaireSchema} from './vspQschema.js'; + +describe('Software Product Details: ', function () { + + let currentSoftwareProduct = {}, categories = [], finalizedLicenseModelList, licenseAgreementList, featureGroupsList, vspQschema; + let dummyFunc = () => {}; + + before(function() { + currentSoftwareProduct = { + id: 'D4774719D085414E9D5642D1ACD59D20', + name: 'VSP', + description: 'dfdf', + category: 'category1', + subCategory: 'category1.subCategory', + vendorId: 'VLM_ID1', + vendorName: 'VLM1', + licensingVersion: '1.0', + licensingData: {} + }; + categories = [{ + uniqueId: 'category1', + subcategories: [{ + uniqueId: 'subCategory' + }] + }, { + uniqueId: 'category2', + subcategories: [{ + uniqueId: 'subCategory2' + }] + }]; + finalizedLicenseModelList = [{ + id: 'VLM_ID1', + name: 'VLM1' + }]; + licenseAgreementList = [{id: 'LA_ID1'}, {id: 'LA_ID2'}]; + featureGroupsList = [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ]; + vspQschema = vspQuestionnaireSchema; + }); + + it('should mapper exist', () => { + expect(mapStateToProps).toExist(); + }); + + it('should mapper return vsp basic data', () => { + var obj = { + softwareProduct: { + softwareProductEditor: { + data: currentSoftwareProduct + }, + softwareProductCategories: categories, + softwareProductQuestionnaire: { + qdata: {}, + qschema: vspQschema + } + }, + finalizedLicenseModelList: finalizedLicenseModelList, + licenseModel: { + licenseAgreement: { + licenseAgreementList: [] + }, + featureGroup: { + featureGroupsList: [] + } + } + }; + + var result = mapStateToProps(obj); + expect(result.currentSoftwareProduct).toEqual(currentSoftwareProduct); + expect(result.finalizedLicenseModelList).toEqual(finalizedLicenseModelList); + expect(result.finalizedLicenseModelList.length).toBeGreaterThan(0); + expect(finalizedLicenseModelList).toInclude({ + id: result.currentSoftwareProduct.vendorId, + name: result.currentSoftwareProduct.vendorName + }); + expect(result.softwareProductCategories).toEqual(categories); + expect(result.licenseAgreementList).toEqual([]); + expect(result.featureGroupsList).toEqual([]); + expect(result.qdata).toEqual({}); + expect(result.qschema).toEqual(vspQschema); + expect(result.isReadOnlyMode).toEqual(true); + }); + + it('should mapper return vsp data with selected licenseAgreement and featureGroup', () => { + let vspWithLicensingData = { + ...currentSoftwareProduct, + licensingData: { + licenseAgreement: 'LA_ID1', + featureGroups: [{enum: 'FG_ID1', title: 'FG1'}] + } + }; + var obj = { + softwareProduct: { + softwareProductEditor: { + data: vspWithLicensingData + }, + softwareProductCategories: categories, + softwareProductQuestionnaire: { + qdata: {}, + qschema: vspQschema + } + }, + finalizedLicenseModelList: finalizedLicenseModelList, + licenseModel: { + licenseAgreement: { + licenseAgreementList: licenseAgreementList + }, + featureGroup: { + featureGroupsList: featureGroupsList + } + } + }; + + var result = mapStateToProps(obj); + expect(result.currentSoftwareProduct).toEqual(vspWithLicensingData); + expect(result.finalizedLicenseModelList).toEqual(finalizedLicenseModelList); + expect(result.finalizedLicenseModelList.length).toBeGreaterThan(0); + expect(result.finalizedLicenseModelList).toInclude({ + id: result.currentSoftwareProduct.vendorId, + name: result.currentSoftwareProduct.vendorName + }); + expect(result.softwareProductCategories).toEqual(categories); + expect(result.licenseAgreementList).toEqual(licenseAgreementList); + expect(result.licenseAgreementList).toInclude({id: result.currentSoftwareProduct.licensingData.licenseAgreement}); + result.currentSoftwareProduct.licensingData.featureGroups.forEach(fg => { + expect(featureGroupsList).toInclude({ + id: fg.enum, + name: fg.title, + referencingLicenseAgreements: [result.currentSoftwareProduct.licensingData.licenseAgreement] + }); + expect(result.featureGroupsList).toInclude(fg); + }); + expect(result.qdata).toEqual({}); + expect(result.qschema).toEqual(vspQschema); + expect(result.isReadOnlyMode).toEqual(true); + }); + + it('VSP Details view test', () => { + let params = { + currentSoftwareProduct: currentSoftwareProduct, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + var renderer = TestUtils.createRenderer(); + renderer.render( + <SoftwareProductDetailsView + {...params} + onSubmit = {dummyFunc} + onDataChanged = {dummyFunc} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {dummyFunc}/> + ); + let renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + }); + + it('in view: should change vendorId and update vsp licensing-version', done => { + let vspWithLicensingData = { + ...currentSoftwareProduct, + licensingData: { + licenseAgreement: 'LA_ID1', + featureGroups: [{enum: 'FG_ID1', title: 'FG1'}] + } + }; + let params = { + currentSoftwareProduct: vspWithLicensingData, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + const onVendorChangedListener = (deltaData) => { + expect(deltaData.vendorId).toEqual('VLM_ID2'); + expect(deltaData.vendorName).toEqual('VLM2'); + expect(deltaData.licensingVersion).toEqual(''); + expect(deltaData.licensingData).toEqual({}); + done(); + }; + + var vspDetailsView = TestUtils.renderIntoDocument(<SoftwareProductDetailsView + currentSoftwareProduct = {params.currentSoftwareProduct} + softwareProductCategories = {params.softwareProductCategories} + qdata = {params.qdata} + qschema = {params.qschema} + finalizedLicenseModelList = {params.finalizedLicenseModelList} + licenseAgreementList = {params.licenseAgreementList} + featureGroupsList = {params.featureGroupsList} + onSubmit = {dummyFunc} + onDataChanged = {dummyFunc} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {(deltaData) => onVendorChangedListener(deltaData)}/>); + expect(vspDetailsView).toExist(); + vspDetailsView.onVendorParamChanged({vendorId: 'VLM_ID2'}); + }); + + it('in view: should change licensing-version and update licensing data', done => { + let params = { + currentSoftwareProduct: currentSoftwareProduct, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + const onVendorChangedListener = (deltaData) => { + expect(deltaData.vendorId).toEqual('VLM_ID2'); + expect(deltaData.vendorName).toEqual('VLM2'); + expect(deltaData.licensingVersion).toEqual('2.0'); + expect(deltaData.licensingData).toEqual({}); + done(); + }; + + let vspDetailsView = TestUtils.renderIntoDocument(<SoftwareProductDetailsView + {...params} + onSubmit = {dummyFunc} + onDataChanged = {dummyFunc} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {(deltaData) => onVendorChangedListener(deltaData)}/>); + expect(vspDetailsView).toExist(); + vspDetailsView.onVendorParamChanged({vendorId: 'VLM_ID2', licensingVersion: '2.0'}); + }); + + it('in view: should change subcategory', done => { + let params = { + currentSoftwareProduct: currentSoftwareProduct, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + const onDataChangedListener = ({category, subCategory}) => { + expect(category).toEqual('category2'); + expect(subCategory).toEqual('subCategory2'); + done(); + }; + + let vspDetailsView = TestUtils.renderIntoDocument(<SoftwareProductDetailsView + {...params} + onSubmit = {dummyFunc} + onDataChanged = {({category, subCategory}) => onDataChangedListener({category, subCategory})} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {dummyFunc}/>); + expect(vspDetailsView).toExist(); + vspDetailsView.onSelectSubCategory('subCategory2'); + }); + + it('in view: should change feature groups', done => { + let vspWithLicensingData = { + ...currentSoftwareProduct, + licensingData: { + licenseAgreement: 'LA_ID1', + featureGroups: [{enum: 'FG_ID1', title: 'FG1'}] + } + }; + let params = { + currentSoftwareProduct: vspWithLicensingData, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + const onDataChangedListener = ({licensingData}) => { + expect(licensingData.licenseAgreement).toEqual('LA_ID1'); + expect(licensingData.featureGroups).toEqual([ + {enum: 'FG_ID1', title: 'FG1'}, + {enum: 'FG_ID2', title: 'FG2'} + ]); + done(); + }; + + let vspDetailsView = TestUtils.renderIntoDocument(<SoftwareProductDetailsView + {...params} + onSubmit = {dummyFunc} + onDataChanged = {({licensingData}) => onDataChangedListener({licensingData})} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {dummyFunc}/>); + expect(vspDetailsView).toExist(); + vspDetailsView.onFeatureGroupsChanged({featureGroups: [ + {enum: 'FG_ID1', title: 'FG1'}, + {enum: 'FG_ID2', title: 'FG2'} + ]}); + }); + + it('in view: should change license agreement', done => { + let vspWithLicensingData = { + ...currentSoftwareProduct, + licensingData: { + licenseAgreement: 'LA_ID1', + featureGroups: [{enum: 'FG_ID1', title: 'FG1'}] + } + }; + let params = { + currentSoftwareProduct: vspWithLicensingData, + softwareProductCategories: categories, + qdata: {}, + qschema: vspQschema, + finalizedLicenseModelList: [{ + id: 'VLM_ID1', + vendorName: 'VLM1', + version: '2.0', + viewableVersions: ['1.0', '2.0'] + }, { + id: 'VLM_ID2', + vendorName: 'VLM2', + version: '3.0', + viewableVersions: ['1.0', '2.0', '3.0'] + }], + licenseAgreementList: [{id: 'LA_ID1'}, {id: 'LA_ID2'}], + featureGroupsList: [ + {id: 'FG_ID1', name: 'FG1', referencingLicenseAgreements: ['LA_ID1']}, + {id: 'FG_ID2', name: 'FG2', referencingLicenseAgreements: ['LA_ID1']} + ] + }; + const onDataChangedListener = ({licensingData}) => { + expect(licensingData.licenseAgreement).toEqual('LA_ID2'); + expect(licensingData.featureGroups).toEqual([]); + done(); + }; + + let vspDetailsView = TestUtils.renderIntoDocument(<SoftwareProductDetailsView + {...params} + onSubmit = {dummyFunc} + onDataChanged = {({licensingData}) => onDataChangedListener({licensingData})} + onValidityChanged = {dummyFunc} + onQDataChanged = {dummyFunc} + onVendorParamChanged = {dummyFunc}/>); + expect(vspDetailsView).toExist(); + vspDetailsView.onLicensingDataChanged({licenseAgreement: 'LA_ID2', featureGroups: []}); + }); +}); diff --git a/openecomp-ui/test/softwareProduct/details/test.js b/openecomp-ui/test/softwareProduct/details/test.js new file mode 100644 index 0000000000..9803b1611d --- /dev/null +++ b/openecomp-ui/test/softwareProduct/details/test.js @@ -0,0 +1,383 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; + +import SoftwareProductCreationActionHelper from 'sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js'; +import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; +import SoftwareProductCategoriesHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js'; + +describe('Software Product Module Tests', function () { + it('Get Software Products List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductList = [ + { + name: 'VSP1', + description: 'hjhj', + version: '0.1', + id: 'EBADF561B7FA4A788075E1840D0B5971', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '5259EDE4CC814DC9897BA6F69E2C971B', + vendorName: 'Vendor', + checkinStatus: 'CHECK_OUT', + licensingData: { + 'featureGroups': [] + } + }, + { + name: 'VSP2', + description: 'dfdfdfd', + version: '0.1', + id: '2F47447D22DB4C53B020CA1E66201EF2', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '5259EDE4CC814DC9897BA6F69E2C971B', + vendorName: 'Vendor', + checkinStatus: 'CHECK_OUT', + licensingData: { + featureGroups: [] + } + } + ]; + + deepFreeze(softwareProductList); + + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProductList', softwareProductList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal('/onboarding-api/v1.0/vendor-software-products/'); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: softwareProductList}; + }); + + return SoftwareProductActionHelper.fetchSoftwareProductList(store.dispatch).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add Software Product', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductPostRequest = deepFreeze({ + name: 'vsp1', + description: 'string', + vendorId: '1', + vendorName: 'Vendor', + icon: 'icon', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + licensingData: {} + }); + + const softwareProductToAdd = deepFreeze({ + ...softwareProductPostRequest + }); + + const softwareProductIdFromResponse = 'ADDED_ID'; + const softwareProductAfterAdd = deepFreeze({ + ...softwareProductToAdd, + id: softwareProductIdFromResponse + }); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProductList', [softwareProductAfterAdd]); + + mockRest.addHandler('create', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal('/onboarding-api/v1.0/vendor-software-products/'); + expect(data).to.deep.equal(softwareProductPostRequest); + expect(options).to.equal(undefined); + return { + vspId: softwareProductIdFromResponse + }; + }); + + return SoftwareProductCreationActionHelper.createSoftwareProduct(store.dispatch, { + softwareProduct: softwareProductToAdd + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + it('Save Software product', () => { + const softwareProduct = { + name: 'VSP5', + id: '4730033D16C64E3CA556AB0AC4478218', + description: 'A software model for Fortigate.', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '1', + vendorName: 'Vendor', + licensingVersion: '1.0', + icon: 'icon', + licensingData: { + licenceAgreement: '123', + featureGroups: [ + '123', '234' + ] + } + }; + deepFreeze(softwareProduct); + + const store = storeCreator({ + softwareProduct: { + softwareProductEditor: {data: softwareProduct}, + softwareProductQuestionnaire: {qdata: 'test', qschema: {type: 'string'}} + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedSoftwareProductId = softwareProduct.id; + const softwareProductUpdateData = { + ...softwareProduct, + name: 'VSP5_UPDATED', + description: 'A software model for Fortigate._UPDATED' + }; + deepFreeze(softwareProductUpdateData); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProductList', [softwareProductUpdateData]); + const questionnaireData = { + general: { + affinityData: { + affinityGrouping: true, + antiAffinityGrouping: false + } + } + }; + deepFreeze(questionnaireData); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + const expectedData = { + name: 'VSP5_UPDATED', + description: 'A software model for Fortigate._UPDATED', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '1', + vendorName: 'Vendor', + licensingVersion: '1.0', + icon: 'icon', + licensingData: { + licenceAgreement: '123', + featureGroups: [ + '123', '234' + ] + } + }; + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${toBeUpdatedSoftwareProductId}`); + expect(data).to.deep.equal(expectedData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${toBeUpdatedSoftwareProductId}/questionnaire`); + expect(data).to.deep.equal(questionnaireData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductActionHelper.updateSoftwareProduct(store.dispatch, { + softwareProduct: softwareProductUpdateData, + qdata: questionnaireData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + it('Save Software product data only', () => { + const softwareProduct = { + name: 'VSP5', + id: '4730033D16C64E3CA556AB0AC4478218', + description: 'A software model for Fortigate.', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '1', + vendorName: 'Vendor', + licensingVersion: '1.0', + icon: 'icon', + licensingData: { + licenceAgreement: '123', + featureGroups: [ + '123', '234' + ] + } + }; + deepFreeze(softwareProduct); + + const store = storeCreator({ + softwareProduct: { + softwareProductEditor: {data: softwareProduct}, + softwareProductQuestionnaire: {qdata: 'test', qschema: {type: 'string'}} + } + }); + deepFreeze(store.getState()); + const expectedStore = store.getState(); + + const toBeUpdatedSoftwareProductId = softwareProduct.id; + const softwareProductUpdateData = { + ...softwareProduct, + name: 'VSP5_UPDATED', + description: 'A software model for Fortigate._UPDATED' + }; + deepFreeze(softwareProductUpdateData); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + const expectedData = { + name: 'VSP5_UPDATED', + description: 'A software model for Fortigate._UPDATED', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '1', + vendorName: 'Vendor', + licensingVersion: '1.0', + icon: 'icon', + licensingData: { + licenceAgreement: '123', + featureGroups: [ + '123', '234' + ] + } + }; + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${toBeUpdatedSoftwareProductId}`); + expect(data).to.deep.equal(expectedData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductActionHelper.updateSoftwareProductData(store.dispatch, { + softwareProduct: softwareProductUpdateData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Save Software product questionnaire only', () => { + const softwareProduct = { + name: 'VSP5', + id: '4730033D16C64E3CA556AB0AC4478218', + description: 'A software model for Fortigate.', + subCategory: 'resourceNewCategory.network connectivity.virtual links', + category: 'resourceNewCategory.network connectivity', + vendorId: '1', + vendorName: 'Vendor', + icon: 'icon', + licensingData: { + licenceAgreement: '123', + featureGroups: [ + '123', '234' + ] + } + }; + deepFreeze(softwareProduct); + + const store = storeCreator({ + softwareProduct: { + softwareProductEditor: {data: softwareProduct}, + softwareProductQuestionnaire: {qdata: 'test', qschema: {type: 'string'}} + } + }); + deepFreeze(store.getState()); + const expectedStore = store.getState(); + + const toBeUpdatedSoftwareProductId = softwareProduct.id; + const questionnaireData = { + general: { + affinityData: { + affinityGrouping: true, + antiAffinityGrouping: false + } + } + }; + deepFreeze(questionnaireData); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${toBeUpdatedSoftwareProductId}/questionnaire`); + expect(data).to.deep.equal(questionnaireData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductActionHelper.updateSoftwareProductQuestionnaire(store.dispatch, { + softwareProductId: softwareProduct.id, + qdata: questionnaireData + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Handle category without subcategories', () => { + const categories = deepFreeze([ + { + name: 'Resource Category 1', + normalizedName: 'resource category 1', + uniqueId: 'resourceNewCategory.resource category 1', + subcategories: [ + { + name: 'Sub Category for RC 1', + normalizedName: 'sub category for rc 1', + uniqueId: 'resourceNewCategory.resource category 1.sub category for rc 1' + }, + { + name: 'SC4RC2', + normalizedName: 'sc4rc2', + uniqueId: 'resourceNewCategory.resource category 1.sc4rc2' + }, + { + name: 'SC4RC1', + normalizedName: 'sc4rc1', + uniqueId: 'resourceNewCategory.resource category 1.sc4rc1' + } + ] + }, + { + name: 'Eeeeee', + normalizedName: 'eeeeee', + uniqueId: 'resourceNewCategory.eeeeee' + }, + { + name: 'Some Recource', + normalizedName: 'some recource', + uniqueId: 'resourceNewCategory.some recource', + subcategories: [ + { + name: 'Second Sub Category for S', + normalizedName: 'second sub category for s', + uniqueId: 'resourceNewCategory.some recource.second sub category for s' + }, + { + name: 'Sub Category for Some Rec', + normalizedName: 'sub category for some rec', + uniqueId: 'resourceNewCategory.some recource.sub category for some rec' + } + ] + } + ]); + const category = SoftwareProductCategoriesHelper.getCurrentCategoryOfSubCategory('resourceNewCategory.some recource.sub category for some rec', categories); + expect(category).to.equal('resourceNewCategory.some recource'); + }); + +}); + diff --git a/openecomp-ui/test/softwareProduct/details/vspQschema.js b/openecomp-ui/test/softwareProduct/details/vspQschema.js new file mode 100644 index 0000000000..5612b19991 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/details/vspQschema.js @@ -0,0 +1,61 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +export const vspQschema = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'type': 'object', + 'properties': { + 'general': { + 'type': 'object', + 'properties': { + 'affinityData': { + 'type': 'object', + 'properties': { + 'affinityGrouping': {}, + 'antiAffinityGrouping': {} + } + }, + 'availability': { + 'type': 'object', + 'properties': { + 'useAvailabilityZonesForHighAvailability': {} + } + }, + 'regionsData': { + 'type': 'object', + 'properties': { + 'multiRegion': {}, + 'regions': {} + } + }, + 'storageDataReplication': { + 'type': 'object', + 'properties': { + 'storageReplicationAcrossRegion': {}, + 'storageReplicationSize': {}, + 'storageReplicationFrequency': {}, + 'storageReplicationSource': {}, + 'storageReplicationDestination': {} + } + } + } + } + } +}; diff --git a/openecomp-ui/test/softwareProduct/networks/SoftwareProductNetworksView.test.js b/openecomp-ui/test/softwareProduct/networks/SoftwareProductNetworksView.test.js new file mode 100644 index 0000000000..a7f7b2b0c2 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/networks/SoftwareProductNetworksView.test.js @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import React from 'react'; +import TestUtils from 'react-addons-test-utils'; +import {mapStateToProps} from 'sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js'; +import SoftwareProductNetworksView from 'sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx'; +import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js'; + +describe('SoftwareProductNetworks Mapper and View Classes', () => { + it ('mapStateToProps mapper exists', () => { + expect(mapStateToProps).toExist(); + }); + + it ('mapStateToProps data test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + const networksList = [ + { + name:'dummy_net_1', + dhcp:true, + 'id':'7F60CD390458421DA588AF4AD217B93F' + }, + { + name:'dummy_net_2', + dhcp:true, + 'id':'AD217B93F7F60CD390458421DA588AF4' + } + ]; + + var obj = { + softwareProduct: { + softwareProductEditor: { + data:currentSoftwareProduct + }, + softwareProductNetworks: + { + networksList + } + } + }; + var results = mapStateToProps(obj); + expect(results.networksList,).toExist(); + }); + + it ('view simple test', () => { + + const currentSoftwareProduct = { + name: 'VSp', + description: 'dfdf', + vendorName: 'V1', + vendorId: '97B3E2525E0640ACACF87CE6B3753E80', + category: 'resourceNewCategory.application l4+', + subCategory: 'resourceNewCategory.application l4+.database', + id: 'D4774719D085414E9D5642D1ACD59D20', + version: '0.10', + viewableVersions: ['0.1', '0.2'], + status: versionStatusEnum.CHECK_OUT_STATUS, + lockingUser: 'cs0008' + }; + + const networksList = [ + { + name:'dummy_net_1', + dhcp:true, + 'id':'7F60CD390458421DA588AF4AD217B93F' + }, + { + name:'dummy_net_2', + dhcp:true, + 'id':'AD217B93F7F60CD390458421DA588AF4' + } + ]; + + const versionControllerData = { + version: '1', + viewableVersions: [], + status: 'locked', + isCheckedOut: true + }; + + var renderer = TestUtils.createRenderer(); + renderer.render(<SoftwareProductNetworksView networksList={networksList} versionControllerData={versionControllerData} currentSoftwareProduct={currentSoftwareProduct}/>); + var renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toExist(); + + }); + + + +}); diff --git a/openecomp-ui/test/softwareProduct/networks/softwareProductNetworksActionHelper.test.js b/openecomp-ui/test/softwareProduct/networks/softwareProductNetworksActionHelper.test.js new file mode 100644 index 0000000000..2920803c64 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/networks/softwareProductNetworksActionHelper.test.js @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import SoftwareProductNetworksActionHelper from 'sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js'; + +const softwareProductId = '123'; + +describe('Software Product Networks ActionHelper Tests', function () { + it('Get Software Products Networks List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const networksList = [ + { + name:'dummy_net_1', + dhcp:true, + 'id':'7F60CD390458421DA588AF4AD217B93F' + }, + { + name:'dummy_net_2', + dhcp:true, + 'id':'AD217B93F7F60CD390458421DA588AF4' + } + ]; + + deepFreeze(networksList); + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductNetworks.networksList', networksList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`/onboarding-api/v1.0/vendor-software-products/${softwareProductId}/networks`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: networksList}; + }); + + return SoftwareProductNetworksActionHelper.fetchNetworksList(store.dispatch, {softwareProductId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + + }); +}); diff --git a/openecomp-ui/test/softwareProduct/processes/test.js b/openecomp-ui/test/softwareProduct/processes/test.js new file mode 100644 index 0000000000..73f22a7898 --- /dev/null +++ b/openecomp-ui/test/softwareProduct/processes/test.js @@ -0,0 +1,459 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import {expect} from 'chai'; +import deepFreeze from 'deep-freeze'; +import mockRest from 'test-utils/MockRest.js'; +import {cloneAndSet} from 'test-utils/Util.js'; +import {storeCreator} from 'sdc-app/AppStore.js'; +import Configuration from 'sdc-app/config/Configuration.js'; +import SoftwareProductProcessesActionHelper from 'sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js'; + +const softwareProductId = '123'; + +describe('Software Product Processes Module Tests', function () { + + let restPrefix = ''; + + before(function() { + restPrefix = Configuration.get('restPrefix'); + deepFreeze(restPrefix); + }); + + //** + //** ADD + //** + it('Add Software Products Processes', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductPostRequest = { + name: 'Pr1', + description: 'string' + }; + const softwareProductProcessToAdd = { + name: 'Pr1', + description: 'string' + }; + const softwareProductProcessFromResponse = 'ADDED_ID'; + const softwareProductProcessAfterAdd = { + ...softwareProductProcessToAdd, + id: softwareProductProcessFromResponse + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', [softwareProductProcessAfterAdd]); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes`); + expect(data).to.deep.equal(softwareProductPostRequest); + expect(options).to.equal(undefined); + return { + returnCode: 'OK', + value: softwareProductProcessFromResponse + }; + }); + + return SoftwareProductProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId: softwareProductId, + previousProcess: null, + process: softwareProductProcessToAdd + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Add Software Products Processes with uploaded file', () => { + + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductPostRequest = { + name: 'Pr1', + description: 'string' + }; + const softwareProductProcessToAdd = { + name: 'Pr1', + description: 'string', + formData: { + name: 'new artifact name' + } + }; + const softwareProductProcessFromResponse = 'ADDED_ID'; + const softwareProductProcessAfterAdd = { + ...softwareProductProcessToAdd, + id: softwareProductProcessFromResponse + }; + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', [softwareProductProcessAfterAdd]); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes`); + expect(data).to.deep.equal(softwareProductPostRequest); + expect(options).to.equal(undefined); + return { + returnCode: 'OK', + value: softwareProductProcessFromResponse + }; + }); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes/${softwareProductProcessAfterAdd.id}/upload`); + expect(data).to.deep.equal(softwareProductProcessToAdd.formData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId: softwareProductId, + previousProcess: null, + process: softwareProductProcessToAdd + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + //** + //** UPDATE + //** + it('Update Software Products Processes', () => { + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'string', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + } + ]; + deepFreeze(softwareProductProcessesList); + + const store = storeCreator({ + softwareProduct: { + softwareProductProcesses: { + processesList: softwareProductProcessesList + } + } + }); + deepFreeze(store.getState()); + + const toBeUpdatedProcessId = softwareProductProcessesList[0].id; + const previousProcessData = softwareProductProcessesList[0]; + const processUpdateData = { + ...softwareProductProcessesList[0], + name: 'Pr1_UPDATED', + description: 'string_UPDATED' + }; + deepFreeze(processUpdateData); + + const processPutRequest = { + name: 'Pr1_UPDATED', + description: 'string_UPDATED' + }; + deepFreeze(processPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', [processUpdateData]); + + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes/${toBeUpdatedProcessId}`); + expect(data).to.deep.equal(processPutRequest); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId: softwareProductId, + previousProcess: previousProcessData, + process: processUpdateData + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Update Software Products Processes and uploaded file', () => { + const previousProcessData = { + id: 'EBADF561B7FA4A788075E1840D0B5971', + name: 'p1', + description: 'string', + artifactName: 'artifact' + }; + deepFreeze(previousProcessData); + + const store = storeCreator({ + softwareProduct: { + softwareProductProcesses: { + processesList: [previousProcessData] + } + } + }); + deepFreeze(store.getState()); + + const newProcessToUpdate = { + ...previousProcessData, + name: 'new name', + formData: { + name: 'new artifact name' + } + }; + deepFreeze(newProcessToUpdate); + + const newProcessToPutRequest = { + name: newProcessToUpdate.name, + description: previousProcessData.description + }; + deepFreeze(newProcessToPutRequest); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', [newProcessToUpdate]); + + mockRest.addHandler('save', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes/${previousProcessData.id}`); + expect(data).to.deep.equal(newProcessToPutRequest); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + mockRest.addHandler('create', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes/${previousProcessData.id}/upload`); + expect(data).to.deep.equal(newProcessToUpdate.formData); + expect(options).to.equal(undefined); + return {returnCode: 'OK'}; + }); + + return SoftwareProductProcessesActionHelper.saveProcess(store.dispatch, + { + softwareProductId: softwareProductId, + previousProcess: previousProcessData, + process: newProcessToUpdate + } + ).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + //** + //** GET + //** + it('Get Software Products Processes List', () => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'hjhj', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + }, + { + name: 'Pr1', + description: 'hjhj', + id: '2F47447D22DB4C53B020CA1E66201EF2', + artifactName: 'artifact' + } + ]; + + deepFreeze(softwareProductProcessesList); + + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', softwareProductProcessesList); + + mockRest.addHandler('fetch', ({options, data, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes`); + expect(data).to.deep.equal(undefined); + expect(options).to.equal(undefined); + return {results: softwareProductProcessesList}; + }); + + return SoftwareProductProcessesActionHelper.fetchProcessesList(store.dispatch, {softwareProductId}).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + //** + //** DELETE + //** + it('Delete Software Products Processes', () => { + const softwareProductProcessesList = [ + { + name: 'Pr1', + description: 'hjhj', + id: 'EBADF561B7FA4A788075E1840D0B5971', + artifactName: 'artifact' + } + ]; + + deepFreeze(softwareProductProcessesList); + const store = storeCreator({ + softwareProduct: { + softwareProductProcesses: { + processesList: softwareProductProcessesList + } + } + }); + + const processId = 'EBADF561B7FA4A788075E1840D0B5971'; + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesList', []); + + mockRest.addHandler('destroy', ({data, options, baseUrl}) => { + expect(baseUrl).to.equal(`${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/processes/${processId}`); + expect(data).to.equal(undefined); + expect(options).to.equal(undefined); + return { + results: { + returnCode: 'OK' + } + }; + }); + + return SoftwareProductProcessesActionHelper.deleteProcess(store.dispatch, { + process: softwareProductProcessesList[0], + softwareProductId + }).then(() => { + expect(store.getState()).to.deep.equal(expectedStore); + }); + }); + + it('Validating Software Products Processes Delete confirmation', done => { + const store = storeCreator(); + deepFreeze(store.getState()); + + let process = { + id: 'p_id', + name: 'p_name' + }; + deepFreeze(process); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processToDelete', process); + + SoftwareProductProcessesActionHelper.openDeleteProcessesConfirm(store.dispatch, {process}); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); + + it('Validating Software Products Processes Cancel Delete', done => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processToDelete', false); + + SoftwareProductProcessesActionHelper.hideDeleteConfirm(store.dispatch); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); + + //** + //** CREATE/EDIT + //** + it('Validating open Software Products Processes for create', done => { + const store = storeCreator(); + deepFreeze(store.getState()); + + let process = {}; + deepFreeze(process); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesEditor.data', process); + + SoftwareProductProcessesActionHelper.openEditor(store.dispatch); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); + + it('Validating close Software Products Processes from editing mode', done => { + const store = storeCreator(); + deepFreeze(store.getState()); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesEditor', {}); + + SoftwareProductProcessesActionHelper.closeEditor(store.dispatch); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); + + it('Validating open Software Products Processes for editing', done => { + const store = storeCreator(); + deepFreeze(store.getState()); + + let process = {name: 'aa', description: 'xx'}; + deepFreeze(process); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesEditor.data', process); + + SoftwareProductProcessesActionHelper.openEditor(store.dispatch, process); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); + + it('Validating Software Products Processes dataChanged event', done => { + let process = {name: 'aa', description: 'xx'}; + deepFreeze(process); + + const store = storeCreator({ + softwareProduct: { + softwareProductProcesses: { + processesEditor: { + data: process + } + } + } + }); + deepFreeze(store.getState()); + + let deltaData = {name: 'bb'}; + deepFreeze(deltaData); + + let expectedProcess = {name: 'bb', description: 'xx'}; + deepFreeze(expectedProcess); + + const expectedStore = cloneAndSet(store.getState(), 'softwareProduct.softwareProductProcesses.processesEditor.data', expectedProcess); + + SoftwareProductProcessesActionHelper.processEditorDataChanged(store.dispatch, {deltaData}); + + setTimeout(function(){ + expect(store.getState()).to.deep.equal(expectedStore); + done(); + }, 100); + }); +}); + diff --git a/openecomp-ui/test/utils/errorResponseHandler.test.js b/openecomp-ui/test/utils/errorResponseHandler.test.js new file mode 100644 index 0000000000..fd9dec6a8d --- /dev/null +++ b/openecomp-ui/test/utils/errorResponseHandler.test.js @@ -0,0 +1,135 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import deepFreeze from 'deep-freeze'; +import expect from 'expect'; +import {cloneAndSet} from '../../test-utils/Util.js'; +import store from 'sdc-app/AppStore.js'; +import errorResponseHandler from 'nfvo-utils/ErrorResponseHandler.js'; + +describe('Error Response Handler Util', () => { + + beforeEach(function () { + deepFreeze(store.getState()); + }); + + it('validating error in policyException', done => { + let textStatus = '', errorThrown = ''; + let xhr = { + responseJSON: { + requestError: { + policyException: { + messageId: 'SVC4122', + text: 'Error: Invalid data.' + } + } + } + }; + deepFreeze(xhr); + + const errorNotification = { + type: 'error', title: 'Error: SVC4122', msg: 'Error: Invalid data.', timeout: undefined, + validationResponse: undefined + }; + const expectedStore = cloneAndSet(store.getState(), 'notification', errorNotification); + + errorResponseHandler(xhr, textStatus, errorThrown); + + setTimeout(function () { + expect(store.getState()).toEqual(expectedStore); + done(); + }, 100); + }); + + it('validating error in serviceException with variables', done => { + let textStatus = '', errorThrown = ''; + let xhr = { + responseJSON: { + requestError: { + serviceException: { + messageId: 'SVC4122', + text: "Error: Invalid artifact type '%1'.", + variables: ['newType'] + } + } + } + }; + deepFreeze(xhr); + + const errorNotification = { + type: 'error', title: 'Error: SVC4122', msg: 'Error: Invalid artifact type newType.', timeout: undefined, + validationResponse: undefined + }; + const expectedStore = cloneAndSet(store.getState(), 'notification', errorNotification); + + errorResponseHandler(xhr, textStatus, errorThrown); + + setTimeout(function () { + expect(store.getState()).toEqual(expectedStore); + done(); + }, 100); + }); + + it('validating error in response', done => { + let textStatus = '', errorThrown = ''; + let xhr = { + responseJSON: { + status: 'AA', + message: 'Error: Invalid data.' + } + }; + deepFreeze(xhr); + + const errorNotification = { + type: 'error', title: 'AA', msg: 'Error: Invalid data.', timeout: undefined, + validationResponse: undefined + }; + const expectedStore = cloneAndSet(store.getState(), 'notification', errorNotification); + + errorResponseHandler(xhr, textStatus, errorThrown); + + setTimeout(function () { + expect(store.getState()).toEqual(expectedStore); + done(); + }, 100); + }); + + it('validating error in request', done => { + let textStatus = '', errorThrown = ''; + let xhr = { + statusText: '500', + responseText: 'Internal server error.' + }; + deepFreeze(xhr); + + const errorNotification = { + type: 'error', title: '500', msg: 'Internal server error.', timeout: undefined, + validationResponse: undefined + }; + const expectedStore = cloneAndSet(store.getState(), 'notification', errorNotification); + + errorResponseHandler(xhr, textStatus, errorThrown); + + setTimeout(function () { + expect(store.getState()).toEqual(expectedStore); + done(); + }, 100); + }); +}); diff --git a/openecomp-ui/test/utils/restApiUtil.test.js b/openecomp-ui/test/utils/restApiUtil.test.js new file mode 100644 index 0000000000..2a5e69b02e --- /dev/null +++ b/openecomp-ui/test/utils/restApiUtil.test.js @@ -0,0 +1,149 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import expect from 'expect'; +import $ from 'jquery'; +import RestAPIUtil, {makeQueryParams} from 'src/nfvo-utils/RestAPIUtil'; + +const URL = 'http://bla.ble.blu/'; + +describe('RestAPIUtil Util class', () => { + + beforeEach(()=> { + $.ajax = (options) => options; + }); + + it('RestAPIUtil does exist', () => { + expect(RestAPIUtil).toExist(); + }); + + it('RestAPIUtil makeQueryParams does exist', () => { + expect(makeQueryParams).toExist(); + }); + + it('RestAPIUtil makeQueryParams params', () => { + const pageStart = 1, pageSize = 25; + const response = makeQueryParams({pagination: {pageStart, pageSize}}); + expect(response.pageStart).toBe(pageStart); + expect(response.pageSize).toBe(pageSize); + }); + + it('normal basic fetch', () => { + const response = RestAPIUtil.fetch(URL); + expect(response).toExist(); + }); + + it('no url', function () { + expect(function () { + RestAPIUtil.fetch(); + }).toThrow(/url/); + }); + + it('fetch with pagination', () => { + const pageStart = 1, pageSize = 25; + const response = RestAPIUtil.fetch(URL, {pagination: {pageStart, pageSize}}); + expect(response.pagination).toExist(); + expect(response.url).toInclude(`?pageStart=${pageStart}&pageSize=${pageSize}`); + }); + + it('fetch with sorting', () => { + const sortField = 'name', sortDir = 'ASCENDING'; + const response = RestAPIUtil.fetch(URL, {sorting: {sortField, sortDir}}); + expect(response.sorting).toExist(); + expect(response.url).toInclude(`?sortField=${sortField}&sortDir=${sortDir}`); + }); + + it('fetch with filtering', () => { + const baseFilter = [ + { + criterionValue: 'service', + fieldName: 'Brand', + operator: 'EQUALS', + type: 'STRING' + }, + { + criterionValue: 'resource', + fieldName: 'Brand', + operator: 'EQUALS', + type: 'STRING' + } + ]; + const response = RestAPIUtil.fetch(URL, {filtering: {filterCriteria: baseFilter, logicalRelation: 'OR'}}); + expect(response.filtering).toExist(); + expect(response.url).toInclude('?filter='); + }); + + it('fetch with qParams', () => { + const response = RestAPIUtil.fetch(URL, {qParams: {pageStart: 1, pageSize: 10}}); + expect(response.qParams).toExist(); + }); + + it('fetch with url on options', () => { + const response = RestAPIUtil.fetch(URL, {url:'12345', qParams: {pageStart: 1, pageSize: 10}}); + expect(response.qParams).toExist(); + }); + + it('fetch with url path param', () => { + let someData = 'data'; + const response = RestAPIUtil.fetch(`${URL}{someData}/`, {params: {someData}}); + expect(response.url).toInclude(`/${someData}/`); + }); + + it('fetch with url undefined path param', () => { + const response = RestAPIUtil.fetch(`${URL}{someData}/`, {params: {someData: undefined}}); + expect(response.url).toInclude('/undefined/'); + }); + + it('normal basic create', () => { + const response = RestAPIUtil.create(URL); + expect(response).toExist(); + }); + + it('create with FormData', () => { + let formData = new FormData(); + formData.append('username', 'Chris'); + const response = RestAPIUtil.create(URL, formData); + expect(response).toExist(); + }); + + it('create with FormData with md5', () => { + let formData = new FormData(); + formData.append('username', 'Chris'); + const response = RestAPIUtil.create(URL, formData, {md5: true}); + expect(response).toExist(); + }); + + it('create with file', () => { + let progressCallback = () => {}; + const response = RestAPIUtil.create(URL, {}, {progressCallback, fileSize: 123}); + expect(response).toExist(); + }); + + it('normal basic save', () => { + const response = RestAPIUtil.save(URL); + expect(response).toExist(); + }); + + it('normal basic delete', () => { + const response = RestAPIUtil.destroy(URL); + expect(response).toExist(); + }); + +}); diff --git a/openecomp-ui/test/utils/uuid.test.js b/openecomp-ui/test/utils/uuid.test.js new file mode 100644 index 0000000000..cd55baadea --- /dev/null +++ b/openecomp-ui/test/utils/uuid.test.js @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import uuid from 'src/nfvo-utils/UUID.js'; +import expect from 'expect'; + +describe('UUID', () => { + + it('function does exist', () => { + expect(uuid).toExist(); + }); + + it('generate UUID synchronously', () => { + let result = uuid(undefined, true); + expect(result).toExist(); + }); + + it('generate UUID synchronously with number', () => { + let result = uuid(5, true); + expect(result).toExist(); + }); + + it('generate UUID synchronously with number', () => { + let result = uuid(1, true); + expect(result).toExist(); + }); + + it('generate UUID asynchronously', done => { + uuid().then(result => { + expect(result).toExist(); + done(); + }); + }); + +}); diff --git a/openecomp-ui/tests.webpack.js b/openecomp-ui/tests.webpack.js new file mode 100644 index 0000000000..10b54fed83 --- /dev/null +++ b/openecomp-ui/tests.webpack.js @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var testContext = require.context('./test', true, /.test\.js$/); +testContext.keys().forEach(testContext); + +var utilsContext = require.context('./src/nfvo-utils', true, /\.js$/); +utilsContext.keys().forEach(utilsContext); + +var componentsContext = require.context('./src/nfvo-components', true, /\.(js|jsx)$/); +componentsContext.keys().forEach(componentsContext); + +var flowsCodeContext = require.context('./src/sdc-app/flows', true, /\.(js|jsx)$/); +flowsCodeContext.keys().forEach(flowsCodeContext); + +var onBoardingCodeContext = require.context('./src/sdc-app/onboarding', true, /\.(js|jsx)$/); +onBoardingCodeContext.keys().forEach(onBoardingCodeContext); + diff --git a/openecomp-ui/tools/gulp/deployment/gulpfile.js b/openecomp-ui/tools/gulp/deployment/gulpfile.js new file mode 100644 index 0000000000..99389108bb --- /dev/null +++ b/openecomp-ui/tools/gulp/deployment/gulpfile.js @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var gulp = require('gulp'); +var i18nUpdateTask = require('./tools/gulp/tasks/i18nUpdate'); + +gulp.task('i18nUpdate', function() { + + return i18nUpdateTask({ + warDir: process.cwd(), + lang: 'en' + }); +}); + +gulp.task('default', ['i18nUpdate']); + diff --git a/openecomp-ui/tools/gulp/deployment/package.json b/openecomp-ui/tools/gulp/deployment/package.json new file mode 100644 index 0000000000..3bad0374bf --- /dev/null +++ b/openecomp-ui/tools/gulp/deployment/package.json @@ -0,0 +1,23 @@ +{ + "name": "sdc-client-tools", + "version": "9.3.0", + "description": "Service Designer & Catalog Client Tools", + "dependencies": {}, + "devDependencies": { + "bluebird": "^2.10.1", + "gulp": "^3.9.0", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.4", + "prompt": "^0.2.14" + }, + "author": "ECOMP", + "license": "LicenseRef-LICENSE", + "scripts": { + "start": "gulp run", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "engines": { + "node": ">=0.12.7", + "npm": ">=2.11.3" + } +} diff --git a/openecomp-ui/tools/gulp/deployment/tools/gulp/tasks/i18nUpdate.js b/openecomp-ui/tools/gulp/deployment/tools/gulp/tasks/i18nUpdate.js new file mode 100644 index 0000000000..a3cae5b018 --- /dev/null +++ b/openecomp-ui/tools/gulp/deployment/tools/gulp/tasks/i18nUpdate.js @@ -0,0 +1,171 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var gulp, replace, rename, fs, prompt, Promise; + +function mergePromptOptions(options) { + + return new Promise(function(resolve, reject) { + var lang = options.lang; + var warDir = options.warDir; + prompt.start(); + prompt.get([ + { + description: 'Enter war directory', + default: warDir, + name: 'warDir' + }, + { + description: 'Enter locale.json parent directory name', + default: lang, + name: 'lang' + } + ], function (err, result) { + + if(err) { + reject(new Error('mergePromptOptions::>\n ' + err)); + return; + } + + var warDir = result.warDir; + var lang = result.lang; + + console.log('\nlocale.json parent directory name> "' + lang + '"'); + console.log('war director>"' + warDir + '"'); + + resolve({ + warDir: warDir, + lang: lang + }); + }); + }); +} + +function isBundleExists(path) { + return new Promise(function(resolve) { + fs.stat(path, function(err) { + resolve(null == err); + /*if null == err then file exists.*/ + }); + }); +} + +function copyEnglishBundle(enBundlePath, lang) { + return new Promise(function(resolve, reject) { + gulp.src(enBundlePath, {base: './'}) + .pipe(rename({basename: 'bundle_' + lang})) + .pipe(gulp.dest('./')) + .on('end', function() { + resolve(); + }) + .on('error', function(err) { + reject(new Error('copyEnglishBundle::>\n ' + err)); + }); + }); +} + +function getLocaleContent(localePath) { + + return new Promise(function(resolve, reject) { + fs.readFile(localePath, {encoding: 'utf-8'}, function(err,data){ + if(err) { + reject('getLocaleContent()::>\n ' + err); + return; + } + resolve(data); + }); + }); + +} + +function extractLocaleJsonContent(localeDataStr) { + + var localeJsonStrI18nStartIdx = localeDataStr.indexOf('I18N_IDENTIFIER_START'); + var localeJsonStrI18nEndIdx = localeDataStr.indexOf('I18N_IDENTIFIER_END'); + + if(-1 === localeJsonStrI18nStartIdx || -1 === localeJsonStrI18nEndIdx) { + return Promise.reject(new Error('extractLocaleJsonContent::> localeDataStr must contain %I18N_IDENTIFIER_START% and %I18N_IDENTIFIER_END%')); + } + + var localeJsonStr = localeDataStr.substring( + localeDataStr.indexOf('{', localeJsonStrI18nStartIdx), + localeDataStr.lastIndexOf('}', localeJsonStrI18nEndIdx) + 1 + ); + + try { + JSON.parse(localeJsonStr); + } catch(e) { + return Promise.reject(new Error('extractLocaleJsonContent::> localeDataStr must contain a valid json between %I18N_IDENTIFIER_START% and %I18N_IDENTIFIER_END%=>' + e)); + } + + return Promise.resolve(localeJsonStr); +} + +function setBundleLocaleContent(bundlePath, localeJsonStr) { + return new Promise(function(resolve, reject) { + gulp.src(bundlePath, {base: './'}) + .pipe(replace(/I18N_IDENTIFIER_START(.|[\r\n])*?I18N_IDENTIFIER_END/i, function(expr) { + return expr.substring(0, expr.indexOf('{')) + localeJsonStr + expr.substring(expr.lastIndexOf('}') + 1); + })) + .pipe(gulp.dest('./')) + .on('end', function() { + resolve(); + }) + .on('error', function(err) { + reject(new Error('setBundleLocaleContent::>\n ' + err)); + }); + }); +} + + +function update(options) { + + gulp = require('gulp'); + replace = require('gulp-replace'); + rename = require('gulp-rename'); + fs = require('fs'); + prompt = require('prompt'); + Promise = require('bluebird'); + + return mergePromptOptions(options).then(function(mergedOptions) { + var lang = mergedOptions.lang; + var warDir = mergedOptions.warDir; + + var bundlePath = warDir + '/js/bundle_' + lang + '.js'; + var localePath = warDir + '/i18n/' + lang + '/locale.json'; + + return isBundleExists(bundlePath) + .then(function(isBundleExist) { + var englishBundlePath; + if(!isBundleExist) { + englishBundlePath = warDir + '/js/bundle_en.js'; + return copyEnglishBundle(englishBundlePath, lang); + } + }) + .then(getLocaleContent.bind(null, localePath)) + .then(extractLocaleJsonContent) + .then(setBundleLocaleContent.bind(null, bundlePath)); + }); + +} + + + +module.exports = update; diff --git a/openecomp-ui/tools/gulp/tasks/i18n.js b/openecomp-ui/tools/gulp/tasks/i18n.js new file mode 100644 index 0000000000..38b2a02dcc --- /dev/null +++ b/openecomp-ui/tools/gulp/tasks/i18n.js @@ -0,0 +1,115 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var gulp = require('gulp'); +var fs = require('fs'); +var replace = require('gulp-replace'); +var clean = require('gulp-clean'); +var mkdirp = require('mkdirp'); + +/** + * + * @param options.outputPath + * @param options.localesPath + * @param options.lang = options.lang + * + * @returns {string} + */ +function composeLocalesDirPath(options) { + return options.outputPath + options.localesPath + options.lang; +} + +/** + * + * @param options.outputPath + * @param options.localesPath + * @param options.lang + * + * @returns {string} + */ +function composeLocaleFilePath(options) { + return composeLocalesDirPath(options) + '/locale.json'; +} + + +/** + * @param options + * @param options.outputPath + * @param options.localesPath + * @param options.lang = options.lang + * + */ +function ensureLocalesDir(options) { + + return new Promise(function (resolve, reject) { + mkdirp(composeLocalesDirPath(options), function (err) { + if (err) { + reject(err); + } + else { + resolve(); + } + }); + }); + +} + +/** + * + * @param options + * @param options.outputPath + * @param options.localesPath + * @param options.lang = options.lang + * + */ +function i18nTask(options) { + + var i18nJson = {}; + + function addWord(expr) { + var word = expr.substring('i18n(\''.length, expr.length - 1); + i18nJson[word] = word; + return expr; + } + + return ensureLocalesDir(options).then(function () { + return new Promise(function(resolve, reject) { + gulp.src(options.outputPath + '**/*.js', {base: './'}) + .pipe(replace(/i18n\('.*?'/g, addWord)) + .pipe(clean()) + .pipe(gulp.dest('./')) + .on('end', function () { + + var i18nJsonWrapper = { dataWrapperArr: ["I18N_IDENTIFIER_START", i18nJson, "I18N_IDENTIFIER_END"] , i18nDataIdx: 1}; + + fs.writeFile(composeLocaleFilePath(options), JSON.stringify(i18nJsonWrapper), function (err) { + if (err) { + reject(err); + } + else resolve(); + }); + }).on('error', function (err) { + reject(err); + }); + }); + }); +} + +module.exports = i18nTask; diff --git a/openecomp-ui/tools/gulp/tasks/prod.js b/openecomp-ui/tools/gulp/tasks/prod.js new file mode 100644 index 0000000000..d66b841d2a --- /dev/null +++ b/openecomp-ui/tools/gulp/tasks/prod.js @@ -0,0 +1,100 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +var gulp, replace, Promise, webpack, webpackProductionConfig; + +var supportedLanguages = ['en']; + +function start(options) { + + var promises = [buildIndex(options)]; + supportedLanguages.forEach(function (lang) { + promises.push(bundleJS(options, lang)); + }); + return Promise.all(promises); +} + +function bundleJS(options, lang) { + return new Promise(function (resolve, reject) { + var prodConfig = webpackProductionConfig; + prodConfig.resolve.alias.i18nJson = options.outDir + '/i18n/' + lang + '/locale.json'; + prodConfig.output.filename = jsFileByLang(options.outFileName, lang); + webpack(prodConfig, function (err, stats) { + console.log('[webpack:build]', stats.toString()); + if (err || stats.hasErrors()) { + console.log('bundleJS : Failure!!', '\n -language: ', lang); + reject(err || stats.toJson().errors); + } + else { + console.log('bundleJS : Done', '\n -language: ', lang); + resolve(); + } + }); + }); +} + +function buildIndex(options) { + + return new Promise(function (resolve, reject) { + + var stream = gulp.src(options.outDir + '/index.html'); + + stream.pipe(replace(/\/\/<!--prod:delete-->(.|[\r\n])*?<!--\/prod:delete-->/g, ''))//in script occurrences. + .pipe(replace(/<!--prod:delete-->(.|[\r\n])*?<!--\/prod:delete-->/g, ''))//out of script occurrences. + .pipe(replace(/<!--prod:add(-->)?/g, '')) + .pipe(replace(/\/\/<!--prod:supported-langs-->(.|[\r\n])*?<!--\/prod:supported-langs-->/g, supportedLanguages.map(function (val) { + return "'" + val + "'"; + }).toString())) + .pipe(gulp.dest(options.outDir)) + .on('end', function () { + console.log('buildIndex : Done'); + resolve(); + }) + .on('error', function (e) { + console.log('buildIndex : Failure!!'); + reject(e); + }); + }); + +} + +function jsFileByLang(fileName, lang) { + return fileName.replace(/.js$/, '_' + lang + '.js'); +} + +/** + * @param options + * @param options.outFileName optional <default build> + */ +function prodTask(options) { + + gulp = require('gulp'); + replace = require('gulp-replace'); + Promise = require('bluebird'); + webpack = require('webpack'); + webpackProductionConfig = options.webpackProductionConfig; + + return start({ + outFileName: options.outFileName || '[name].js', + outDir: options.outDir + }); +} + +module.exports = prodTask; diff --git a/openecomp-ui/tools/webpack/config-json-loader/index.js b/openecomp-ui/tools/webpack/config-json-loader/index.js new file mode 100644 index 0000000000..bf34533f67 --- /dev/null +++ b/openecomp-ui/tools/webpack/config-json-loader/index.js @@ -0,0 +1,26 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +module.exports = function(content) { + var config = JSON.parse(content); + var build = process.env.BUILD_NUMBER || '0'; + config.build = build; + return JSON.stringify(config); +}; diff --git a/openecomp-ui/webapp-heat-validation/META-INF/context.xml b/openecomp-ui/webapp-heat-validation/META-INF/context.xml new file mode 100644 index 0000000000..2ada7fc48f --- /dev/null +++ b/openecomp-ui/webapp-heat-validation/META-INF/context.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Context> + <Valve className="org.apache.catalina.valves.rewrite.RewriteValve"/> +</Context> diff --git a/openecomp-ui/webapp-heat-validation/WEB-INF/jetty-web.xml b/openecomp-ui/webapp-heat-validation/WEB-INF/jetty-web.xml new file mode 100644 index 0000000000..bc3bfa62f0 --- /dev/null +++ b/openecomp-ui/webapp-heat-validation/WEB-INF/jetty-web.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE Configure PUBLIC + "-//Mort Bay Consulting//DTD Configure//EN" + "http://www.eclipse.org/jetty/configure_9_0.dtd"> + +<Configure class="org.eclipse.jetty.webapp.WebAppContext"> + <Set name="contextPath">/heat-validation</Set> +</Configure> diff --git a/openecomp-ui/webapp-heat-validation/WEB-INF/rewrite.config b/openecomp-ui/webapp-heat-validation/WEB-INF/rewrite.config new file mode 100644 index 0000000000..b5e1a31493 --- /dev/null +++ b/openecomp-ui/webapp-heat-validation/WEB-INF/rewrite.config @@ -0,0 +1,2 @@ +RewriteRule ^.*\..*$ - [L] +RewriteRule ^.*$ /heat.html [L] diff --git a/openecomp-ui/webapp-heat-validation/WEB-INF/web.xml b/openecomp-ui/webapp-heat-validation/WEB-INF/web.xml new file mode 100644 index 0000000000..f84519eee3 --- /dev/null +++ b/openecomp-ui/webapp-heat-validation/WEB-INF/web.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<web-app xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + version="3.0"> + + <display-name>Heat-Validation</display-name> + + <welcome-file-list> + <welcome-file>heat.html</welcome-file> + </welcome-file-list> + + <servlet> + <servlet-name>js</servlet-name> + <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class> + + <init-param> + <param-name>cacheControl</param-name> + <param-value>public, no-cache</param-value> + </init-param> + <init-param> + <param-name>etags</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <servlet-mapping> + <servlet-name>js</servlet-name> + <url-pattern>*.js</url-pattern> + </servlet-mapping> + + <servlet> + <servlet-name>resources</servlet-name> + <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class> + + <init-param> + <param-name>cacheControl</param-name> + <param-value>public, max-age=31536000</param-value> + </init-param> + <init-param> + <param-name>etags</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <servlet-mapping> + <servlet-name>resources</servlet-name> + <url-pattern>/</url-pattern> + </servlet-mapping> + +</web-app> diff --git a/openecomp-ui/webapp-heat-validation/heat-validation.xml b/openecomp-ui/webapp-heat-validation/heat-validation.xml new file mode 100644 index 0000000000..404dcf23f3 --- /dev/null +++ b/openecomp-ui/webapp-heat-validation/heat-validation.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> + + +<Configure id="heatValidationWebapp" class="org.eclipse.jetty.webapp.WebAppContext"> + + <Set name="contextPath">/heat-validation</Set> + <Set name="war"><Property name="jetty.webapps" default="."/>/heat-validation.war</Set> + + <!-- Enable WebSocket container --> + <Call name="setAttribute"> + <Arg>org.eclipse.jetty.websocket.jsr356</Arg> + <Arg type="Boolean">true</Arg> + </Call> + + <Set name="gzipHandler"> + <New class="org.eclipse.jetty.server.handler.gzip.GzipHandler"> + <Set name="minGzipSize">2048</Set> + </New> + </Set> + + + <Get name="securityHandler"> + <Set name="loginService"> + <New class="org.eclipse.jetty.security.HashLoginService"> + <Set name="name">Test Realm</Set> + <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set> + <!-- To enable reload of realm when properties change, uncomment the following lines --> + <!-- changing refreshInterval (in seconds) as desired --> + <!-- + <Set name="refreshInterval">5</Set> + <Call name="start"></Call> + --> + </New> + </Set> + <Set name="authenticator"> + <New class="org.eclipse.jetty.security.authentication.FormAuthenticator"> + <Set name="alwaysSaveUri">true</Set> + </New> + </Set> + <Set name="checkWelcomeFiles">true</Set> + </Get> + + +</Configure> diff --git a/openecomp-ui/webapp-onboarding/META-INF/context.xml b/openecomp-ui/webapp-onboarding/META-INF/context.xml new file mode 100644 index 0000000000..2ada7fc48f --- /dev/null +++ b/openecomp-ui/webapp-onboarding/META-INF/context.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Context> + <Valve className="org.apache.catalina.valves.rewrite.RewriteValve"/> +</Context> diff --git a/openecomp-ui/webapp-onboarding/WEB-INF/jetty-web.xml b/openecomp-ui/webapp-onboarding/WEB-INF/jetty-web.xml new file mode 100644 index 0000000000..0a9d33a940 --- /dev/null +++ b/openecomp-ui/webapp-onboarding/WEB-INF/jetty-web.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE Configure PUBLIC + "-//Mort Bay Consulting//DTD Configure//EN" + "http://www.eclipse.org/jetty/configure_9_0.dtd"> + +<Configure class="org.eclipse.jetty.webapp.WebAppContext"> + <Set name="contextPath">/onboarding</Set> +</Configure> diff --git a/openecomp-ui/webapp-onboarding/WEB-INF/rewrite.config b/openecomp-ui/webapp-onboarding/WEB-INF/rewrite.config new file mode 100644 index 0000000000..90893bb545 --- /dev/null +++ b/openecomp-ui/webapp-onboarding/WEB-INF/rewrite.config @@ -0,0 +1,2 @@ +RewriteRule ^.*\..*$ - [L] +RewriteRule ^.*$ /index.html [L] diff --git a/openecomp-ui/webapp-onboarding/WEB-INF/web.xml b/openecomp-ui/webapp-onboarding/WEB-INF/web.xml new file mode 100644 index 0000000000..6dd619fbbc --- /dev/null +++ b/openecomp-ui/webapp-onboarding/WEB-INF/web.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<web-app xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + version="3.0"> + + <display-name>ASDC</display-name> + + <welcome-file-list> + <welcome-file>index.html</welcome-file> + </welcome-file-list> + + <servlet> + <servlet-name>js</servlet-name> + <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class> + + <init-param> + <param-name>cacheControl</param-name> + <param-value>public, no-cache</param-value> + </init-param> + <init-param> + <param-name>etags</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <servlet-mapping> + <servlet-name>js</servlet-name> + <url-pattern>*.js</url-pattern> + </servlet-mapping> + + <servlet> + <servlet-name>resources</servlet-name> + <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class> + + <init-param> + <param-name>cacheControl</param-name> + <param-value>public, max-age=31536000</param-value> + </init-param> + <init-param> + <param-name>etags</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <servlet-mapping> + <servlet-name>resources</servlet-name> + <url-pattern>/</url-pattern> + </servlet-mapping> + +</web-app> diff --git a/openecomp-ui/webapp-onboarding/onboarding.xml b/openecomp-ui/webapp-onboarding/onboarding.xml new file mode 100644 index 0000000000..50df896a4b --- /dev/null +++ b/openecomp-ui/webapp-onboarding/onboarding.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> + + +<Configure id="onBoardWebapp" class="org.eclipse.jetty.webapp.WebAppContext"> + + <Set name="contextPath">/onboarding</Set> + <Set name="war"><Property name="jetty.webapps" default="."/>/onboarding-fe.war</Set> + + <!-- Enable WebSocket container --> + <Call name="setAttribute"> + <Arg>org.eclipse.jetty.websocket.jsr356</Arg> + <Arg type="Boolean">true</Arg> + </Call> + + <Set name="gzipHandler"> + <New class="org.eclipse.jetty.server.handler.gzip.GzipHandler"> + <Set name="minGzipSize">2048</Set> + </New> + </Set> + + + <Get name="securityHandler"> + <Set name="loginService"> + <New class="org.eclipse.jetty.security.HashLoginService"> + <Set name="name">Test Realm</Set> + <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set> + <!-- To enable reload of realm when properties change, uncomment the following lines --> + <!-- changing refreshInterval (in seconds) as desired --> + <!-- + <Set name="refreshInterval">5</Set> + <Call name="start"></Call> + --> + </New> + </Set> + <Set name="authenticator"> + <New class="org.eclipse.jetty.security.authentication.FormAuthenticator"> + <Set name="alwaysSaveUri">true</Set> + </New> + </Set> + <Set name="checkWelcomeFiles">true</Set> + </Get> + + +</Configure> diff --git a/openecomp-ui/webpack.config.js b/openecomp-ui/webpack.config.js new file mode 100644 index 0000000000..2cccba8f0f --- /dev/null +++ b/openecomp-ui/webpack.config.js @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +'use strict'; + +var path = require('path'); +var webpack = require('webpack'); + +var localDevConfig = {}; +try { + localDevConfig = require('./devConfig'); +} catch(e) {} +var devConfig = Object.assign({}, require('./devConfig.defaults'), localDevConfig); +var devPort = process.env.PORT || devConfig.port; +var latestProgress = 0; + +module.exports = { + devtool: 'eval-source-map', + entry: { + bundle: [ + 'sdc-app/sdc.app.jsx', + `webpack-dev-server/client?http://localhost:${devPort}`, + 'webpack/hot/only-dev-server' + ], + 'punch-outs': [ + 'sdc-app/punch-outs.js', + `webpack-dev-server/client?http://localhost:${devPort}`, + 'webpack/hot/only-dev-server' + ], + 'heat-validation': [ + 'sdc-app/heatValidation.app.jsx', + `webpack-dev-server/client?http://localhost:${devPort}`, + 'webpack/hot/only-dev-server' + ] + }, + resolve: { + root: [path.resolve('.')], + alias: { + i18nJson: 'nfvo-utils/i18n/locale.json', + 'nfvo-utils': 'src/nfvo-utils', + 'nfvo-components': 'src/nfvo-components', + 'sdc-app': 'src/sdc-app' + } + }, + output: { + path: path.join(__dirname, 'dist/dev'), + publicPath: `http://localhost:${devPort}/onboarding/`, + filename: '[name].js' + }, + devServer: { + port: devPort, + historyApiFallback: true, + publicPath: `http://localhost:${devPort}/onboarding/`, + contentBase: path.join(__dirname, 'dist/dev'), + hot: true, + progress: true, + inline: true, + debug: true, + stats: { + colors: true + } + }, + module: { + preLoaders: [ + {test: /\.(js|jsx)$/, loader: 'source-map-loader', exclude: /node_modules/} + ], + loaders: [ + {test: /\.(js|jsx)$/, loaders: ['react-hot', 'babel-loader', 'eslint-loader'], exclude: /node_modules/}, + {test: /\.(css|scss)$/, loaders: ['style', 'css?sourceMap', 'sass?sourceMap']}, + + // required for font icons + {test: /\.(woff|woff2)(\?.*)?$/, loader: 'url-loader?limit=16384&mimetype=application/font-woff' }, + {test: /\.(ttf|eot|otf)(\?.*)?$/, loader: 'file-loader' }, + {test: /\.(png|jpg|svg)(\?.*)?$/, loader: 'url-loader?limit=16384'}, + + {test: /\.json$/, loaders: ['json']}, + {test: /\.html$/, loaders: ['html']} + ] + }, + eslint: { + configFile: './.eslintrc', + emitError: true, + emitWarning: true + }, + plugins: [ + new webpack.DefinePlugin({ + DEV: true, + DEBUG: true + }), + new webpack.HotModuleReplacementPlugin(), + new webpack.ProgressPlugin(function (percentage, msg) { + if (percentage == 0) { + latestProgress = 0; + console.log(); //new line + } + var progressVal = (percentage * 100).toFixed(0); + if (progressVal > latestProgress) { + latestProgress = progressVal + //process.stdout.clearLine(); + process.stdout.write(msg + ' ' + progressVal + '%\r'); + } + }) + ] + +}; |