From: Laurence Maultsby Date: Fri, 6 Jan 2017 16:01:19 +0000 (+0000) Subject: Package Mananger X-Git-Tag: v1.1.0~27^2 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FUI.git;a=commitdiff_plain;h=b06607173b5bafa999594cdc1e60a825f578e8e6 Package Mananger Signed-off-by: Laurence Maultsby --- diff --git a/skyquake/plugins/composer/api/composer.js b/skyquake/plugins/composer/api/composer.js index d9dc5e2f5..ce128609c 100644 --- a/skyquake/plugins/composer/api/composer.js +++ b/skyquake/plugins/composer/api/composer.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,13 @@ var rp = require('request-promise'); var utils = require('../../../framework/core/api_utils/utils.js'); var constants = require('../../../framework/core/api_utils/constants.js'); var _ = require('underscore'); +var URL = require('url'); +var uuid = require('uuid'); var APIVersion = '/v1'; -var Composer = {}; +var PackageFileHandler = require('./packageFileHandler.js'); +var Composer = {}; +var FileManager = {}; var DataCenters = {}; // Catalog module methods Composer.get = function(req) { @@ -298,4 +302,295 @@ Composer.update = function(req) { }); }); }; -module.exports = Composer; + +Composer.upload = function(req) { + console.log(' Uploading file', req.file.originalname, 'as', req.file.filename); + var api_server = req.query['api_server']; + // dev_download_server is for testing purposes. + // It is the direct IP address of the Node server where the + // package will be hosted. + var download_host = req.query['dev_download_server']; + + if (!download_host) { + download_host = req.protocol + '://' + req.headers.host; + } + + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/api/operations/package-create', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true, + json: true, + body: { + input: { + 'external-url': download_host + '/composer/upload/' + req.file.filename, + 'package-type': 'VNFD', + 'package-id': uuid() + } + } + }) + ]).then(function(result) { + var data = {}; + data['transaction_id'] = result[0].body['output']['transaction-id']; + + // Add a status checker on the transaction and then to delete the file later + PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']); + + // Return status to composer UI to update the status. + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: data + }); + }).catch(function(error) { + var res = {}; + console.log('Problem with Composer.upload', error); + res.statusCode = error.statusCode || 500; + res.errorMessage = { + error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error + }; + reject(res); + }); + }); +}; +Composer.addFile = function(req) { + console.log(' Uploading file', req.file.originalname, 'as', req.file.filename); + var api_server = req.query['api_server']; + var download_host = req.query['dev_download_server']; + var package_id = req.query['package_id']; + var package_type = req.query['package_type'].toUpperCase(); + var package_path = req.query['package_path']; + if (!download_host) { + download_host = req.protocol + '://' + req.headers.host; + } + + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/api/operations/package-file-add', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true, + json: true, + body: { + input: { + 'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename, + 'package-type': package_type, + 'package-id': package_id, + 'package-path': package_path + '/' + req.file.filename + } + } + }) + ]).then(function(result) { + var data = {}; + data['transaction_id'] = result[0].body['output']['transaction-id']; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: data + }); + }).catch(function(error) { + var res = {}; + console.log('Problem with Composer.upload', error); + res.statusCode = error.statusCode || 500; + res.errorMessage = { + error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error + }; + reject(res); + }); + }); +} + +Composer.exportPackage = function(req) { + // /api/operations/package-export + var api_server = req.query['api_server']; + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/api/operations/package-export', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true, + json: true, + body: { "input": req.body} + }) + ]).then(function(result) { + var data = {}; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: result[0].body + }); + }).catch(function(error) { + var res = {}; + console.log('Problem with Composer.exportPackage', error); + res.statusCode = error.statusCode || 500; + res.errorMessage = { + error: error + }; + reject(res); + }); + }); +} + +FileManager.get = function(req) { + var api_server = req.query['api_server']; + var type = req.query['package_type'] && req.query['package_type'].toUpperCase(); + var id = req.query['package_id']; + var downloadUrl = req.query['url']; + var path = req.query['package_path']; + var payload = { + "input": { + "package-type": type, + "package-id": id + } + } + if(req.method == 'GET') { + if(downloadUrl && path) { + payload.input['external-url'] = downloadUrl; + payload.input['package-path'] = path; + return download(payload); + } else { + return list(payload); + } + } + if(req.method == 'DELETE') { + payload.input['package-path'] = path; + return deleteFile(payload) + } + + function deleteFile(payload) { + return new Promise(function(resolve, reject) { + rp({ + uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-delete', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + json: payload, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function(data) { + if (utils.validateResponse('FileManager.delete', data.error, data, data.body, resolve, reject)) { + resolve({ + statusCode: data.statusCode, + data: data.body + }); + } + }) + }) + } + function download(payload) { + return new Promise(function(resolve, reject) { + rp({ + uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-add', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + json: payload, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function(data) { + if (utils.validateResponse('FileManager.get', data.error, data, data.body, resolve, reject)) { + resolve({ + statusCode: data.statusCode, + data: data.body + }); + } + }) + }) + } + function list(payload) { + return new Promise(function(resolve, reject) { + rp({ + uri: utils.confdPort(api_server) + '/api/operations/get-package-endpoint', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + json: payload, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function(data) { + if (utils.validateResponse('FileManager.get', data.error, data, data.body, resolve, reject)) { + var endpoint = null; + var parsedEndpoint = null; + try { + endpoint = data.body.output.endpoint + } catch(e) { + console.log('Something went wrong with the FileManager.get data that was returned'); + reject({}); + } + parsedEndpoint = URL.parse(endpoint); + rp({ + uri: api_server + ':' + parsedEndpoint.port + parsedEndpoint.path, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.get('Authorization') + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function(data) { + if (utils.validateResponse('FileManager.get', data.error, data, data.body, resolve, reject)) { + resolve({ + statusCode: data.statusCode, + data: data.body + }); + } + }) + } + }) + }) + } +} +FileManager.job = function(req) { + var api_server = req.query["api_server"]; + var uri = utils.confdPort(api_server); + var url = '/api/operational/download-jobs'; + var id = req.params['id']; + return new Promise(function(resolve, reject) { + request({ + url: uri + url + '?deep', + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.get('Authorization') + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }, function(error, response, body) { + if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) { + var data = JSON.parse(response.body)['rw-pkg-mgmt:download-jobs']; + var returnData = []; + data && data.job.map(function(d) { + if(d['package-id'] == id) { + returnData.push(d) + } + }) + resolve({ + statusCode: response.statusCode, + data: returnData + }) + }; + }) + }) +} +module.exports = { + Composer:Composer, + FileManager: FileManager +}; diff --git a/skyquake/plugins/composer/api/packageFileHandler.js b/skyquake/plugins/composer/api/packageFileHandler.js new file mode 100644 index 000000000..f199610a7 --- /dev/null +++ b/skyquake/plugins/composer/api/packageFileHandler.js @@ -0,0 +1,59 @@ +var request = require('request'); +var Promise = require('bluebird'); +var rp = require('request-promise'); +var utils = require('../../../framework/core/api_utils/utils.js'); +var constants = require('../../../framework/core/api_utils/constants.js'); +var fs = require('fs'); +var _ = require('lodash'); + +PackageFileHandler = {}; + +function deleteFile(filename) { + setTimeout(function() { + fs.unlinkSync(constants.BASE_PACKAGE_UPLOAD_DESTINATION + filename); + }, constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS); +}; + +function checkStatus(req, transactionId) { + var upload_server = req.query['upload_server']; + var headers = _.extend({}, + { + 'Authorization': req.get('Authorization') + } + ); + request({ + url: upload_server + ':' + constants.PACKAGE_MANAGER_SERVER_PORT + '/api/upload/' + transactionId + '/state', + type: 'GET', + headers: headers, + forever: constants.FOREVER_ON, + rejectUnauthorized: false + }, function(error, response, body) { + if (error) { + console.log('Error checking status for transaction', transactionId, '. Will delete file', req.file.filename, ' in ', constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS ,' milliseconds'); + deleteFile(req.file.filename); + } else { + var jsonStatus = null; + if (typeof body == 'string' || body instanceof String) { + jsonStatus = JSON.parse(body); + } else { + jsonStatus = body; + } + + if (jsonStatus.status && (jsonStatus.status == 'success' || jsonStatus.status == 'failure')) { + console.log('Transaction ', transactionId, ' completed with status ', jsonStatus.status ,'. Will delete file', req.file.filename, ' in ', constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS ,' milliseconds'); + deleteFile(req.file.filename); + } else { + setTimeout(function() { + checkStatus(req, transactionId); + }, constants.PACKAGE_FILE_ONBOARD_TRANSACTION_STATUS_CHECK_DELAY_MILLISECONDS); + } + } + }); +}; + +PackageFileHandler.checkCreatePackageStatusAndHandleFile = function(req, transactionId) { + checkStatus(req, transactionId); +}; + + +module.exports = PackageFileHandler; \ No newline at end of file diff --git a/skyquake/plugins/composer/config.json b/skyquake/plugins/composer/config.json index 2a8a51665..80a2a0c4d 100644 --- a/skyquake/plugins/composer/config.json +++ b/skyquake/plugins/composer/config.json @@ -1,4 +1,6 @@ { + "copyright": "2016 RIFT.IO Inc", + "license": "Apache 2.0", "root": "public", "name": "Catalog", "dashboard" : "./src/components/ComposerApp.js", diff --git a/skyquake/plugins/composer/package.json b/skyquake/plugins/composer/package.json index 1cf415a2a..6a01af1e4 100644 --- a/skyquake/plugins/composer/package.json +++ b/skyquake/plugins/composer/package.json @@ -26,8 +26,10 @@ "grunt-cli": "^0.1.13", "jquery": "^2.1.4", "loaders.css": "^0.1.1", - "lodash": "^3.10.1", + "lodash": "^4.0.0", + "mkdirp": "^0.5.1", "moment": "^2.10.6", + "multer": "^1.2.0", "normalize.css": "^3.0.3", "numeral": "^1.5.3", "object-assign": "^4.0.1", @@ -39,8 +41,10 @@ "react-crouton": "^0.2.7", "react-dom": "^0.14.3", "react-popout": "^0.4.0", + "react-treeview": "^0.4.2", "request-promise": "^3.0.0", - "require-json": "0.0.1" + "require-json": "0.0.1", + "uuid": "^3.0.0" }, "devDependencies": { "babel-core": "^6.4.5", diff --git a/skyquake/plugins/composer/routes.js b/skyquake/plugins/composer/routes.js index af67bfa4d..9fcf10479 100644 --- a/skyquake/plugins/composer/routes.js +++ b/skyquake/plugins/composer/routes.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,10 +15,62 @@ * limitations under the License. * */ -var router = require('express').Router(); +var express = require('express'); +var router = express.Router(); var cors = require('cors'); var utils = require('../../framework/core/api_utils/utils.js') -var Composer = require('./api/composer.js'); +var constants = require('../../framework/core/api_utils/constants.js'); +var C = require('./api/composer.js'); +var Composer = C.Composer; +var FileManager = C.FileManager; +var multer = require('multer'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); + +var storage = multer.diskStorage({ + // destination: 'upload/packages/', + destination: function(req, file, cb) { + var dir = constants.BASE_PACKAGE_UPLOAD_DESTINATION; + if (req.query['package_id']) { + dir += req.query['package_id'] + '/'; + } + if (!fs.existsSync(dir)){ + mkdirp(dir, function(err) { + if (err) { + console.log('Error creating folder for uploads. All systems FAIL!'); + throw err; + } + cb(null, dir); + }); + } else { + cb(null, dir); + } + }, + filename: function (req, file, cb) { + if (req.query['package_id']) { + cb(null, file.originalname); + } else { + cb(null, Date.now() + '_' + file.fieldname + '_' + file.originalname); + } + }, + // limits: { + // fieldNameSize: 100, + // fieldSize: 10000, + // fields: Infinity, + // fileSize: 10000, + // files: Infinity + // parts: Infinity + // headerPairs: 2000 + // } +}); + + +var upload = multer({ + storage: storage +}); + + router.get('/api/catalog', cors(), function(req, res) { Composer.get(req).then(function(data) { utils.sendSuccessResponse(data, res); @@ -51,5 +103,53 @@ router.put('/api/catalog/:catalogType/:id', cors(), function(req, res) { res.send(error.errorMessage); }); }); +router.post('/upload', cors(), upload.single('package'), function (req, res, next) { + Composer.upload(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +router.use('/upload', cors(), express.static('upload/packages')); + + + +router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) { + Composer.addFile(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.get('/api/file-manager', cors(), function(req, res) { + FileManager.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}) +router.get('/api/file-manager/jobs/:id', cors(), function(req, res) { + FileManager.job(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +router.delete('/api/file-manager', cors(), function(req, res) { + FileManager.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.post('/api/package-export', cors(), function (req, res, next) { + Composer.exportPackage(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); module.exports = router; diff --git a/skyquake/plugins/composer/scripts/install.sh b/skyquake/plugins/composer/scripts/install.sh index 76e5e753b..9d27f5eca 100755 --- a/skyquake/plugins/composer/scripts/install.sh +++ b/skyquake/plugins/composer/scripts/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -# +# # Copyright 2016 RIFT.IO Inc # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/skyquake/plugins/composer/src/src/actions/ComposerAppActions.js b/skyquake/plugins/composer/src/src/actions/ComposerAppActions.js index 03e784a4e..80ec497c6 100644 --- a/skyquake/plugins/composer/src/src/actions/ComposerAppActions.js +++ b/skyquake/plugins/composer/src/src/actions/ComposerAppActions.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,9 +24,10 @@ import alt from '../alt'; class ComposerAppActions { constructor() { - this.generateActions('showError', 'clearError', 'setDragState', 'propertySelected', 'showJsonViewer', 'closeJsonViewer', 'selectModel', 'outlineModel', 'clearSelection', 'enterFullScreenMode', 'exitFullScreenMode'); + this.generateActions('showError', 'clearError', 'setDragState', 'propertySelected', 'showJsonViewer', 'closeJsonViewer', 'selectModel', 'outlineModel', 'clearSelection', 'enterFullScreenMode', 'exitFullScreenMode', + 'showAssets', 'showDescriptor'); } } -export default alt.createActions(ComposerAppActions); \ No newline at end of file +export default alt.createActions(ComposerAppActions); diff --git a/skyquake/plugins/composer/src/src/components/Button.js b/skyquake/plugins/composer/src/src/components/Button.js index 7a5a7f54f..76a569f86 100644 --- a/skyquake/plugins/composer/src/src/components/Button.js +++ b/skyquake/plugins/composer/src/src/components/Button.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,8 +59,8 @@ const Button = React.createClass({ const className = ClassNames(this.props.className, 'Button'); return (
- - {label} + { src ? : null } + {label}
); } diff --git a/skyquake/plugins/composer/src/src/components/CanvasPanel.js b/skyquake/plugins/composer/src/src/components/CanvasPanel.js index 6b04bd918..96904ea16 100644 --- a/skyquake/plugins/composer/src/src/components/CanvasPanel.js +++ b/skyquake/plugins/composer/src/src/components/CanvasPanel.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,8 @@ import EditForwardingGraphPaths from './EditorForwardingGraph/EditForwardingGrap import SelectionManager from '../libraries/SelectionManager' import DescriptorModelIconFactory from '../libraries/model/IconFactory' +import FileManager from './filemanager/FileManager.jsx'; + import '../styles/CanvasPanel.scss' const CanvasPanel = React.createClass({ @@ -69,8 +71,22 @@ const CanvasPanel = React.createClass({ var req = require.context("../", true, /^\.\/.*\.svg$/); const hasItem = this.props.containers.length !== 0; const isEditingNSD = DescriptorModelFactory.isNetworkService(this.props.containers[0]); + const isDescriptorView = (this.props.panelTabShown == 'descriptor'); const hasNoCatalogs = this.props.hasNoCatalogs; const bodyComponent = hasItem ? : messages.canvasWelcome(); + const viewFiles = this.props.panelTabShown == 'assets'; + const viewButtonTabs = !hasItem ? null : ( +
+
+ + +
+
+ ) return (
@@ -79,18 +95,23 @@ const CanvasPanel = React.createClass({ {this.props.title}
+ {viewButtonTabs}
- {hasNoCatalogs ? null : bodyComponent} + {hasNoCatalogs ? null : viewFiles ? : bodyComponent}
- - + { + isDescriptorView ? + + : null + } +
); }, onDragOver(event) { - const isDraggingFiles = _.contains(event.dataTransfer.types, 'Files'); + const isDraggingFiles = _.includes(event.dataTransfer.types, 'Files'); if (!isDraggingFiles) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; diff --git a/skyquake/plugins/composer/src/src/components/CatalogPanel.js b/skyquake/plugins/composer/src/src/components/CatalogPanel.js index 535a928b4..d7ebf0f43 100644 --- a/skyquake/plugins/composer/src/src/components/CatalogPanel.js +++ b/skyquake/plugins/composer/src/src/components/CatalogPanel.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -167,7 +167,7 @@ const CatalogPanel = React.createClass({ uiTransientState.isDrop = false; uiTransientState.isDragging = true; uiTransientState.wasTrayOpen = this.state.isTrayOpen; - uiTransientState.isDraggingFiles = _.contains(e.dataTransfer.types, 'Files'); + uiTransientState.isDraggingFiles = _.includes(e.dataTransfer.types, 'Files'); const dragState = ComposerAppStore.getState().drag || {}; if (uiTransientState.isDraggingFiles || (dragState.type === 'catalog-item')) { CatalogPanelTrayActions.open(); diff --git a/skyquake/plugins/composer/src/src/components/ComposerApp.js b/skyquake/plugins/composer/src/src/components/ComposerApp.js index 9e6daa4b4..b5cfa7528 100644 --- a/skyquake/plugins/composer/src/src/components/ComposerApp.js +++ b/skyquake/plugins/composer/src/src/components/ComposerApp.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,11 +47,12 @@ import CatalogDataStore from '../stores/CatalogDataStore' import TooltipManager from '../libraries/TooltipManager' import CatalogItemsActions from '../actions/CatalogItemsActions' import CommonUtils from 'utils/utils.js' - +import FileManagerActions from './filemanager/FileManagerActions'; import 'normalize.css' import '../styles/AppRoot.scss' import 'style/layout.scss' + const resizeManager = new ResizableManager(window); const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty('clearLocalStorage'); @@ -59,6 +60,7 @@ const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty( const preventDefault = e => e.preventDefault(); const clearDragState = () => ComposerAppActions.setDragState(null); + const ComposerApp = React.createClass({ mixins: [PureRenderMixin], getInitialState() { @@ -71,6 +73,9 @@ const ComposerApp = React.createClass({ if (clearLocalStorage) { window.localStorage.clear(); } + if(this.item) { + FileManagerActions.openFileManagerSockets(); + } this.state.isLoading = CatalogDataStore.getState().isLoading; ComposerAppStore.listen(this.onChange); CatalogDataStore.listen(this.onCatalogDataChanged); @@ -88,6 +93,7 @@ const ComposerApp = React.createClass({ window.removeEventListener('dragover', preventDefault); window.removeEventListener('drop', preventDefault); window.removeEventListener('drop', clearDragState); + FileManagerActions.closeFileManagerSockets(); // resizeManager automatically registered its event handlers resizeManager.removeAllEventListeners(); ComposerAppStore.unlisten(this.onChange); @@ -178,14 +184,17 @@ const ComposerApp = React.createClass({ const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0; const isLoading = self.state.isLoading; + //Bridge element for Crouton fix. Should eventually put Composer on same flux context + const Bridge = this.state.ComponentBridgeElement; + html = (
+ {AppHeader} -
- + { + (self.state.panelTabShown == 'descriptor') ? + + : null + } + event.stopPropagation()}/> + onClick={event => event.stopPropagation()} + panelTabShown={self.state.panelTabShown}/>
diff --git a/skyquake/plugins/composer/src/src/components/ComposerAppToolbar.js b/skyquake/plugins/composer/src/src/components/ComposerAppToolbar.js index 8515182c4..458e774c8 100644 --- a/skyquake/plugins/composer/src/src/components/ComposerAppToolbar.js +++ b/skyquake/plugins/composer/src/src/components/ComposerAppToolbar.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,8 +142,16 @@ const ComposerAppToolbar = React.createClass({ ); } const hasSelection = SelectionManager.getSelections().length > 0; + if(this.props.panelTabShown != 'descriptor') { + style.pointerEvents = 'none'; + } return (
+ { + (this.props.panelTabShown != 'descriptor') ? +
+ : null + } {(()=>{ if (this.props.isEditingNSD || this.props.isEditingVNFD) { return ( diff --git a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js index b51071510..4ee4345b7 100644 --- a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js +++ b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js @@ -47,12 +47,12 @@ import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg' import '../styles/EditDescriptorModelProperties.scss' function getDescriptorMetaBasicForType(type) { - const basicPropertiesFilter = d => _.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name); + const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name); return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []}; } function getDescriptorMetaAdvancedForType(type) { - const advPropertiesFilter = d => !_.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name); + const advPropertiesFilter = d => !_.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name); return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []}; } diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx b/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx new file mode 100644 index 000000000..ce490a0e0 --- /dev/null +++ b/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx @@ -0,0 +1,258 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +//https://raw.githubusercontent.com/RIFTIO/RIFT.ware/master/rift-shell +import _ from 'lodash' +import React from 'react'; +import ReactDOM from 'react-dom'; +import TreeView from 'react-treeview'; +import TextInput from 'widgets/form_controls/textInput.jsx'; +import Button from '../Button'; +import './FileMananger.scss'; +import FileManagerActions from './FileManagerActions.js'; +import imgSave from '../../../../node_modules/open-iconic/svg/data-transfer-upload.svg' +import {Panel, PanelWrapper} from 'widgets/panel/panel'; +import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx'; +import LoadingIndicator from 'widgets/loading-indicator/loadingIndicator.jsx'; + +import DropZone from 'dropzone' +import Utils from '../../libraries/utils' +import FileManagerUploadDropZone from '../../libraries/FileManagerUploadDropZone'; +let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server; + +const createDropZone = function (action, clickable, type, id, path, dropTarget) { + const dropZone = new FileManagerUploadDropZone(ReactDOM.findDOMNode(dropTarget), [clickable], action, type, id, path); + // dropZone.on('dragover', this.onDragOver); + // dropZone.on('dragend', this.onDragEnd); + // dropZone.on('addedfile', this.onFileAdded); + return dropZone; +}; +//updateFileLocationInput +class FileManager extends React.Component { + constructor(props) { + super(props) + } + componentWillMount() { + // FileManagerActions.openFileManagerSockets() + } + componentWillUnmount() { + // FileManagerActions.closeFileManagerSockets(); + } + generateFolder(data, nesting) { + let nestingLevel = nesting || 1; + + } + deleteFile(name) { + return function(e) { + FileManagerActions.deletePackageFile(name); + } + + } + updateFileLocationInput(name) { + return function(e) { + FileManagerActions.updateFileLocationInput({ + name: name, + value: e.target.value + }); + } + } + sendDownloadFileRequst = (url, path) => { + let self = this; + return function(e) { + if(!url || url == "") { + return self.props.actions.showNotification.defer({type: 'error', msg: 'Value missing in download request'});; + } + let files = self.props.files.data; + let folder = path.split('/'); + let splitUrl = url.split('/'); + let fileName = splitUrl[splitUrl.length - 1]; + folder.pop; + let fullPath = _.cloneDeep(folder); + fullPath.push(fileName); + fullPath = fullPath.join('/'); + folder = folder.join('/'); + let fileIndex = _.findIndex(files[folder], function(f) { + return f.name == fullPath; + }) + if (fileIndex == -1) { + FileManagerActions.sendDownloadFileRequst({ + url: url, + path: path + }); + } else { + self.props.actions.showNotification('It seems you\'re attempting to upload a file with a duplicate file name'); + } + } + } + render() { + let self = this; + let html = ( +
+ + {self.props.files && self.props.files.id && buildList(self, self.props.files) } + +
+ ) + return html; + } + +} + +function buildList(self, data) { + let toReturn = []; + data.id.map(function(k,i) { + toReturn.push (contentFolder(self, data.data[k], k, i, self.props.filesState, self.updateFileLocationInput, self.sendDownloadFileRequst, self.deleteFile)); + }); + return toReturn.reverse(); +} + +function contentFolder(context, folder, path, key, inputState, updateFn, sendDownloadFileRequst, deleteFn) { + let type = context.props.type; + let id = context.props.item.id; + const onboardDropZone = createDropZone.bind(this, FileManagerUploadDropZone.ACTIONS.onboard, '.ComposerAppAddFile.' + path.replace('/', '-'), type, id, path); + return ( + +
+ { + folder.map(function(f, i) { + if( !f.hasOwnProperty('contents') ){ + return contentFile(context, f, path, i, deleteFn); + } + }) + } + + +
+ OR +
+
+ +
+
+ +
+
+ ); +} +class ItemUpload extends React.Component { + constructor(props) { + super(props); + } + componentDidMount() { + if (this.props.dropZone) { + const dropTarget = this; + const dropZone = this.props.dropZone(dropTarget); + } + } + render() { + let {type, id, path, key, ...props} = this.props; + return ( +
+
+ ) + } +} +function contentFile(context, file, path, key, deleteFn) { + const name = stripPath(file.name, path); + const id = context.props.item.id; + const type = context.props.type; + //{`${window.location.protocol}//${API_SERVER}:4567/api/package${type}/${id}/${path}/${name}`} + return ( +
+
+
+
+ {file.status && (file.status == 'IN_PROGRESS' || file.status == 'DOWNLOADING' ) ? : file.status } +
+
+ {name} +
+
+
X
+
+
+ ) +} + +function stripPath(name, path, returnPath) { + let stripSlash = (name.indexOf('/') > -1) ? '/' : ''; + // return name.split(path + stripSlash)[1].replace('/', ''); + let split = name.split(path + stripSlash)[returnPath ? 0 : 1]; + return split ? split.replace('/', '') : name; +} + + + +export default SkyquakeComponent(FileManager) +/** + * Sample Data + */ +// let files = { +// "name": ".", +// "contents": [ +// { +// "name": "pong_vnfd", +// "contents": [ +// { +// "name": "pong_vnfd/checksums.txt", +// "last_modified_time": 1474458399.6218443, +// "byte_size": 168 +// }, +// { +// "name": "pong_vnfd/pong_vnfd.yaml", +// "last_modified_time": 1474458399.6258445, +// "byte_size": 3514 +// }, +// { +// "name": "pong_vnfd/icons", +// "contents": [ +// { +// "name": "pong_vnfd/icons/rift_logo.png", +// "last_modified_time": 1474458399.6218443, +// "byte_size": 1658 +// } +// ], +// "last_modified_time": 1474458399.6218443, +// "byte_size": 3 +// }, +// { +// "name": "pong_vnfd/cloud_init", +// "contents": [ +// { +// "name": "pong_vnfd/cloud_init/pong_cloud_init.cfg", +// "last_modified_time": 1474458399.6258445, +// "byte_size": 227 +// } +// ], +// "last_modified_time": 1474458399.6258445, +// "byte_size": 3 +// } +// ], +// "last_modified_time": 1474458399.6258445, +// "byte_size": 6 +// } +// ], +// "last_modified_time": 1474458399.6218443, +// "byte_size": 3 +// }; diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileManagerActions.js b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerActions.js new file mode 100644 index 000000000..f39c2a91c --- /dev/null +++ b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerActions.js @@ -0,0 +1,31 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import alt from '../../alt'; + +class FileManagerActions { + + constructor() { + this.generateActions('getFilelistSuccess', 'getFilelistError', 'updateFileLocationInput','sendDownloadFileRequst', 'addFileSuccess', 'addFileError','deletePackageFile','deleteFileSuccess','deleteFileError','openDownloadMonitoringSocketSuccess', 'openDownloadMonitoringSocketError', + 'getFilelistSocketSuccess', + 'openFileManagerSockets', 'closeFileManagerSockets'); + } + +} + +export default alt.createActions(FileManagerActions); diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js new file mode 100644 index 000000000..d2d976585 --- /dev/null +++ b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js @@ -0,0 +1,193 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +'use strict'; + +import _ from 'lodash' +import $ from 'jquery' +import alt from '../../alt' +import utils from '../../libraries/utils' +import FileManagerActions from './FileManagerActions' +let Utils = require('utils/utils.js'); +let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server; +let HOST = API_SERVER; +let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000); +const FileManagerSource = { + + getFilelist: function() { + return { + remote: function(state, id, type) { + return new Promise(function(resolve, reject) { + console.log('Getting File Manager'); + $.ajax({ + beforeSend: Utils.addAuthorizationStub, + url: 'api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id, + success: function(data) { + resolve(JSON.parse(data)); + }, + error: function(error) { + if (typeof error == 'string') { + error = JSON.parse(error); + } + reject(error); + } + }).fail(function(xhr){ + //Authentication and the handling of fail states should be wrapped up into a connection class. + Utils.checkAuthentication(xhr.status); + }); + }); + }, + success: FileManagerActions.getFilelistSuccess, + error: FileManagerActions.getFilelistError + } + }, + addFile: function() { + return { + remote: function(state, id, type, path, url) { + return new Promise(function(resolve, reject) { + console.log('Adding file'); + console.log(id, type, path, url); + let splitUrl = url.split('/'); + let fileName = splitUrl[splitUrl.length -1]; + $.ajax({ + beforeSend: Utils.addAuthorizationStub, + url: 'api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id + '&package_path=' + path + '/' + fileName + '&url=' + url, + success: function(data) { + resolve({ + data:data, + path: path, + fileName: fileName + }); + }, + error: function(error) { + if (typeof error == 'string') { + error = JSON.parse(error); + } + reject(error); + } + }).fail(function(xhr){ + //Authentication and the handling of fail states should be wrapped up into a connection class. + Utils.checkAuthentication(xhr.status); + }); + }); + }, + success: FileManagerActions.addFileSuccess, + error: FileManagerActions.addFileError + } + }, + deleteFile: function() { + return { + remote: function(state, id, type, path) { + return new Promise(function(resolve, reject) { + $.ajax({ + method: 'DELETE', + beforeSend: Utils.addAuthorizationStub, + url: 'api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id + '&package_path=' + path , + success: function(data) { + resolve({ + data: data, + path: path + }); + }, + error: function(error) { + if (typeof error == 'string') { + error = JSON.parse(error); + } + reject(error); + } + }).fail(function(xhr){ + //Authentication and the handling of fail states should be wrapped up into a connection class. + Utils.checkAuthentication(xhr.status); + }); + }); + }, + success: FileManagerActions.deleteFileSuccess, + error: FileManagerActions.deleteFileError + } + }, + updateFile: function() { + return { + remote: function(state, file) { + return new Promise(function(resolve, reject) { + console.log('Getting File Manager'); + if(file) { + console.log('Updating single file'); + } + if(!file) { + console.log('Update all files') + } + resolve({}); + }); + }, + success: FileManagerActions.getFilelistSuccess, + error: FileManagerActions.getFilelistError + } + }, + openDownloadMonitoringSocket: function() { + return { + remote: function(state, packageID) { + return new Promise(function(resolve, reject) { + //api/operational/download-jobs/job/ + $.ajax({ + url: '/socket-polling?api_server=' + API_SERVER , + type: 'POST', + beforeSend: Utils.addAuthorizationStub, + data: { + url: 'http://localhost:8000/composer/api/file-manager/jobs/' + packageID + '?api_server=' + API_SERVER, + }, + success: function(data, textStatus, jqXHR) { + Utils.checkAndResolveSocketRequest(data, resolve, reject); + } + }).fail(function(xhr){ + //Authentication and the handling of fail states should be wrapped up into a connection class. + Utils.checkAuthentication(xhr.status); + }); + }); + }, + success: FileManagerActions.openDownloadMonitoringSocketSuccess, + error: FileManagerActions.openDownloadMonitoringSocketError + } + }, + openFileMonitoringSocket: function() { + return { + remote: function(state, id, type) { + return new Promise(function(resolve, reject) { + //api/operational/download-jobs/job/ + $.ajax({ + url: '/socket-polling?api_server=' + API_SERVER , + type: 'POST', + beforeSend: Utils.addAuthorizationStub, + data: { + url: 'http://localhost:8000/composer/api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id + }, + success: function(data, textStatus, jqXHR) { + Utils.checkAndResolveSocketRequest(data, resolve, reject); + } + }).fail(function(xhr){ + //Authentication and the handling of fail states should be wrapped up into a connection class. + Utils.checkAuthentication(xhr.status); + }); + }); + }, + success: FileManagerActions.getFilelistSocketSuccess, + error: FileManagerActions.getFilelistError + } + } +}; + +export default FileManagerSource; diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileMananger.scss b/skyquake/plugins/composer/src/src/components/filemanager/FileMananger.scss new file mode 100644 index 000000000..136c7f51f --- /dev/null +++ b/skyquake/plugins/composer/src/src/components/filemanager/FileMananger.scss @@ -0,0 +1,171 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@import 'style/_colors.scss'; + +.FileManager { + width:600px; + .skyquakePanel { + display:block; + -ms-flex:0; + flex:0; + &-wrapper { + height:auto; + } + &-body { + padding: 0.5rem; + } + } + .addFileSection { + background:$gray; + margin:1rem 0; + &-body { + padding-bottom: 1rem; + } + .inputSection { + display:-ms-flexbox; + display:flex; + -ms-flex-align: end; + align-items: flex-end; + padding:0 0 0 0.5rem; + margin:0.5rem 0; + .sqTextInput { + margin-bottom:0rem; + input { + font-size:10px; + height:auto; + } + } + &:last-child { + } + } + .ComposerAppAddFile { + span { + color: black; + } + } + } + .tree-view { + margin:0 0.5rem; + margin-bottom: 0.5rem; + padding:0 0.5rem; + /* border-top:1px solid $gray-lightest;*/ + /* border-left:0.5px solid $gray-lightest;* + /* border-bottom:1px solid white;*/ + /* border: 1px solid rgba(143, 143, 143, 0.5);*/ + /* background-color: rgba(147, 161, 161, 0.5);*/ + border-radius: 3px; + font-size:10px; + &_item { + padding:0.5rem; + display:-ms-flexbox; + display:flex; + -ms-flex-align: center; + align-items: center; + color: #586e75; + text-transform: uppercase; + font-size:10px; + background:white; + } + &_children { + margin-left:0; + /* background: $gray;*/ + padding: 0.125rem; + margin-bottom: 0.5rem; + /* background: $gray-light;*/ + &-collapsed { + height: 0px; + overflow: hidden; + } + } + &_arrow{ + height: 2rem; + width: 2rem; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + -ms-flex-align: center; + align-items: center; + + margin-right: 6px; + + font-size: 1.25rem; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + display:none; + + &:after { + content: '\25BE'; + } + &-collapsed { + transform: rotate(-90deg); + } + } + } + .folder { + /* margin:0.5rem 0.5rem;*/ + /* padding: 0.5rem;*/ + /* background-color: rgba(238, 232, 213, 0.33);*/ + /* border-radius:3px;*/ + } + .nested { + & + div { + & > div:not(.nested-alt):not(.folder) { + &:nth-child(even) { + background: $gray-light; + } + &:nth-child(odd) { + background: $gray; + } + } + } + } + .file { + margin: 0.5rem 0; + -ms-flex-pack:justify; + justify-content:space-between; + &-section { + display:-ms-flexbox; + display:flex; + -ms-flex-pack:justify; + justify-content:space-between; + &>div { + margin:0 0.5rem; + } + } + &-info{ + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + -ms-flex-align: center; + align-items: center; + } + &-status { + padding-right:0.5rem; + } + &-name { + color:#586e75; + } + } +} diff --git a/skyquake/plugins/composer/src/src/libraries/CatalogPackageManagerUploadDropZone.js b/skyquake/plugins/composer/src/src/libraries/CatalogPackageManagerUploadDropZone.js index 5f1ee24ab..87afb6afa 100644 --- a/skyquake/plugins/composer/src/src/libraries/CatalogPackageManagerUploadDropZone.js +++ b/skyquake/plugins/composer/src/src/libraries/CatalogPackageManagerUploadDropZone.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,18 +35,24 @@ const ACTIONS = { }; function getCatalogPackageManagerServerOrigin() { - return Utils.getSearchParams(window.location).upload_server + ':4567'; + // return Utils.getSearchParams(window.location).upload_server + ':4567'; + return window.location.origin; } function initializeDropZone(element = '#dropzone', button = false, action = ACTIONS.onboard) { + let Auth = 'Basic ' + window.sessionStorage.getItem("auth"); + let dev_download_server = Utils.getSearchParams(window.location).dev_download_server; DropZone.autoDiscover = false; return new DropZone(element, { - paramName: 'descriptor', + paramName: 'package', url() { if (action === ACTIONS.update) { return getCatalogPackageManagerServerOrigin() + '/api/update'; } - return getCatalogPackageManagerServerOrigin() + '/api/upload'; + return getCatalogPackageManagerServerOrigin() + '/composer/upload?api_server=' + Utils.getSearchParams(window.location).api_server + '&upload_server=' + Utils.getSearchParams(window.location).upload_server + ( dev_download_server ? '&dev_download_server=' + dev_download_server : ''); + }, + headers: { + 'Authorization': Auth }, maxFilesize: 10000000000, clickable: button, diff --git a/skyquake/plugins/composer/src/src/libraries/FileManagerUploadDropZone.js b/skyquake/plugins/composer/src/src/libraries/FileManagerUploadDropZone.js new file mode 100644 index 000000000..963e57ee1 --- /dev/null +++ b/skyquake/plugins/composer/src/src/libraries/FileManagerUploadDropZone.js @@ -0,0 +1,136 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/** + * Created by onvelocity on 10/27/15. + */ + +import guid from '../libraries/guid' +import DropZone from 'dropzone' +import Utils from '../libraries/utils' +import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'; +import FileManagerActions from '../components/filemanager/FileManagerActions.js'; + +/** + * This class is responsible for wiring the DropZone.js to our React actions. + */ + +const ACTIONS = { + onboard: 'onboard', + update: 'update' +}; + +function getCatalogPackageManagerServerOrigin() { + // return Utils.getSearchParams(window.location).upload_server + ':4567'; + return window.location.origin; +} + +function initializeDropZone(element = '#dropzone', button = false, action = ACTIONS.onboard, type, id, path) { + let Auth = 'Basic ' + window.sessionStorage.getItem("auth"); + let dev_download_server = Utils.getSearchParams(window.location).dev_download_server; + DropZone.autoDiscover = false; + return new DropZone(element, { + paramName: 'package', + url() { + if (action === ACTIONS.update) { + return getCatalogPackageManagerServerOrigin() + '/api/update'; + } + return getCatalogPackageManagerServerOrigin() + '/composer/api/file-manager?api_server=' + Utils.getSearchParams(window.location).api_server + '&package_type=' + type + '&package_id=' + id + '&package_path=' + path + ( dev_download_server ? '&dev_download_server=' + dev_download_server : ''); + }, + headers: { + 'Authorization': Auth + }, + maxFilesize: 10000000000, + clickable: button, + autoProcessQueue: true, + previewTemplate: '', + sending(file, xhr, formData) { + // NOTE ie11 does not get this form data + formData.append('id', file.id); + FileManagerActions.addFileSuccess({ + fileName: file.name, + path: path + }) + }, + error(file, errorMessage) { + const response = { + state: file, + data: { + status: 'upload-error', + message: errorMessage + } + }; + CatalogPackageManagerActions.uploadCatalogPackageError(response); + }, + success(file) { + const data = JSON.parse(file.xhr.responseText); + data.status = 'upload-success'; + const response = { + state: file, + data: data + }; + //CatalogPackageManagerActions.uploadCatalogPackageStatusUpdated(response); + }, + addedfile(file) { + file.id = file.id || guid(); + file.riftAction = action; + //CatalogPackageManagerActions.uploadCatalogPackage(file); + }, + thumbnail(file, dataUrl) { + const response = { + state: file, + data: { + status: 'upload-thumbnail', + dataUrl: dataUrl + } + }; + //CatalogPackageManagerActions.uploadCatalogPackageStatusUpdated(response); + }, + uploadprogress(file, progress, bytesSent) { + // FileManagerActions.addFileSuccess({ + // path: path, + // fileName: file.name + // }); + const response = { + state: file, + data: { + status: 'upload-progress', + progress: progress, + bytesSent: bytesSent + } + }; + //CatalogPackageManagerActions.uploadCatalogPackageStatusUpdated(response); + } + }); +} + +export default class CatalogPackageManagerUploadDropZone { + + constructor(element, button, action, type, id, path) { + this.dropZone = initializeDropZone(element, button, action, type, id, path); + } + + static get ACTIONS() { + return ACTIONS; + } + + on(eventName, eventCallback) { + this.dropZone.on(eventName, eventCallback); + } + +} diff --git a/skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js b/skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js new file mode 100644 index 000000000..6b8862c94 --- /dev/null +++ b/skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js @@ -0,0 +1,113 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import guid from '../libraries/guid' +import DropZone from 'dropzone' +import Utils from '../libraries/utils' +import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions' +import ReactDOM from 'react-dom' +import $ from 'jquery' + +const API_SERVER = Utils.getSearchParams(window.location).api_server; + + + + +export default class PackageManager { + constructor(element, button, action) { + this.stagingArea = { + packages: { + ids: [] + } + } + this.stagingAreaMonitor = null; + } + createStagingArea(type, name) { + return $.ajax({ + url: Utils.getSearchParams(window.location).api_server + ':8008/api/operations/create-staging-area', + type: 'POST', + data: { + "input" : { + // Package type not important for package upload. + "package-type": type || "NSD", + "name": name || "Package Staging Area" + } + }, + error: function() { + console.log('Something went wrong creating the staging area: ', arguments) + } + }).then(function(data) { + /* + { + "output": { + "endpoint": "api/upload/85f8e2dc-638b-46e7-89cb-ee8de322066f", + "port": "4568" + } + } + */ + const id = data.output.endpoint.split('/')[2]; + const port = data.output.port; + this.stagingArea.packages.ids.push(id); + this.stagingArea.packages[id] = { + port: port + }; + return data + }) + } + monitoringStagingAreaSocket() { + let self = this; + if(self.stagingAreaMonitor) { + return self.stagingAreaMonitor; + } + new Promise(function(resolve, reject) { + $.ajax({ + url: '/socket-polling?api_server=' + API_SERVER , + type: 'POST', + beforeSend: Utils.addAuthorizationStub, + data: { + url: 'launchpad/api/nsr?api_server=' + API_SERVER + }, + success: function(data, textStatus, jqXHR) { + Utils.checkAndResolveSocketRequest(data, resolve, reject, self.monitoringStagingAreaSocketHandler); + } + }) + }) + + return undefined; + } + monitoringStagingAreaSocketHandler(connection) { + let self = this; + let ws = window.multiplexer.channel(connection); + if (!connection) return; + self.stagingAreaMonitor = connection; + ws.onmessage = function(socket) { + try { + Utils.checkAuthentication(data.statusCode, function() { + ws.close(); + }); + + } catch(e) { + console.log('An exception occurred in monitoringStagingAreaSocketHandler', e) + } + } + } + +} + + + diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js index 8ba295a3d..89d1fe9dc 100644 --- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js +++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js @@ -66,7 +66,7 @@ export default { return !/^(leaf|leaf_list)$/.test(property.type); }, isSimpleList(property = {}) { - return _.contains(DescriptorModelFields.simpleList, property.name); + return _.includes(DescriptorModelFields.simpleList, property.name); }, isPrimativeDataType(property = {}) { const Property = this; @@ -152,7 +152,7 @@ export default { if (uiState.name === 'name') { return changeCase.param(parentMeta.name) + '-' + InstanceCounter.count(parentMeta[':qualified-type']); } - if (_.isArray(parentMeta.key) && _.contains(parentMeta.key, uiState.name)) { + if (_.isArray(parentMeta.key) && _.includes(parentMeta.key, uiState.name)) { if (/uuid/.test(uiState['data-type'])) { return guid(); } diff --git a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js index be11dfcf1..48d697b96 100644 --- a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js +++ b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js @@ -27,8 +27,8 @@ function getApiServerOrigin() { return utils.getSearchParams(window.location).upload_server + ':4567'; } -function ajaxRequest(path, catalogPackage, resolve, reject, method = 'GET') { - $.ajax({ +function ajaxRequest(path, catalogPackage, resolve, reject, method = 'GET', input, urlOverride) { + let options = { url: getApiServerOrigin() + path, type: method, beforeSend: Utils.addAuthorizationStub, @@ -51,17 +51,26 @@ function ajaxRequest(path, catalogPackage, resolve, reject, method = 'GET') { state: catalogPackage }); } - }).fail(function(xhr){ + }; + if(input) { + options.data = input; + } + if (urlOverride) { + options.url = window.location.origin + path; + } + $.ajax(options).fail(function(xhr){ //Authentication and the handling of fail states should be wrapped up into a connection class. Utils.checkAuthentication(xhr.status); }); } + + const CatalogPackageManagerSource = { - requestCatalogPackageDownload: function () { + requestCatalogPackageDownload: function () { return { - remote: function (state, download, format, grammar) { + remote: function (state, download, format, grammar, schema) { return new Promise((resolve, reject) => { // the server does not add a status in the payload // so we add one so that the success handler will @@ -74,9 +83,20 @@ const CatalogPackageManagerSource = { // Backend no longer supports mixed multi-package download. // Probably does not even support multi-package download of same type. // Hence, pick the type from the first element. - const path = '/api/export/' + download['catalogItems'][0]['uiState']['type'] + '?schema=' + format + '&grammar=' + grammar + '&format=yaml&ids=' + download.ids; - ajaxRequest(path, download, setStatusBeforeResolve, reject); - }); + const data = { + "package-type": download['catalogItems'][0]['uiState']['type'].toUpperCase(), + "package-id": download.ids, + "export-format": format && format.toUpperCase() || 'YAML', + "export-grammar": grammar && grammar.toUpperCase() || 'OSM', + "export-schema": schema && schema.toUpperCase() || "RIFT" + } + const path = "/composer/api/package-export?api_server=" + utils.getSearchParams(window.location).api_server; + ajaxRequest(path, download, setStatusBeforeResolve, reject, 'POST', data, true); + }) + //.then(function(data) { + // let filename = data.data.output.filename; + // window.open(getApiServerOrigin() + "/api/export/" + filename, "_blank") + //}); }, success: CatalogPackageManagerActions.downloadCatalogPackageStatusUpdated, error: CatalogPackageManagerActions.downloadCatalogPackageError diff --git a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js index 733e938a6..22d63590f 100644 --- a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js +++ b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js @@ -136,8 +136,9 @@ class CatalogPackageManagerStore { downloadCatalogPackage(data) { let catalogItems = data['selectedItems'] || []; - let format = data['selectedFormat'] || 'mano'; + let schema = data['selectedFormat'] || 'mano'; let grammar = data['selectedGrammar'] || 'osm'; + let format = "YAML"; if (catalogItems.length) { const catalogPackage = Object.assign({}, defaults.downloadPackage, {id: guid()}); catalogPackage.name = catalogItems[0].name; @@ -148,7 +149,7 @@ class CatalogPackageManagerStore { catalogPackage.ids = catalogItems.map(d => d.id).sort().toString(); catalogPackage.catalogItems = catalogItems; this.addPackage(catalogPackage); - this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar).catch(exception); + this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar, schema).catch(exception); } } @@ -189,9 +190,9 @@ function updateStatusInfo(response) { success: false, error: false }; - const responseData = response.data; + const responseData = (response.data.output) ? response.data.output : response.data; const catalogPackage = response.state; - switch(responseData.status) { + switch(response.data.status) { case 'upload-progress': statusInfo.pending = true; statusInfo.progress = parseFloat(responseData.progress) || 0; @@ -201,7 +202,7 @@ function updateStatusInfo(response) { statusInfo.pending = true; statusInfo.progress = 100; statusInfo.message = 'Upload completed.'; - statusInfo.transactionId = responseData.transaction_id; + statusInfo.transactionId = responseData['transaction-id'] || catalogPackage.transactionId; break; case 'upload-error': statusInfo.error = true; @@ -210,7 +211,7 @@ function updateStatusInfo(response) { case 'download-requested': statusInfo.pending = true; statusInfo.progress = 25; - statusInfo.transactionId = responseData.transaction_id; + statusInfo.transactionId = responseData['transaction-id'] || catalogPackage.transactionId; break; case 'pending': statusInfo.pending = true; diff --git a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js index 67e8e8d35..4f278680c 100644 --- a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js +++ b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,23 @@ import SelectionManager from '../libraries/SelectionManager' import CatalogDataStore from '../stores/CatalogDataStore' import isFullScreen from '../libraries/isFullScreen' +import FileManagerSource from '../components/filemanager/FileManagerSource'; +import FileManagerActions from '../components/filemanager/FileManagerActions'; + +import React from 'react'; + +//Hack for crouton fix. Should eventually put composer in skyquake alt context +import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx'; +let NotificationError = null; +class ComponentBridge extends React.Component { + constructor(props) { + super(props); + NotificationError = this.props.flux.actions.global.showNotification; + } + render(){ + return + } +} const getDefault = (name, defaultValue) => { const val = window.localStorage.getItem('defaults-' + name); if (val) { @@ -79,6 +96,10 @@ const uiTransientState = {}; class ComposerAppStore { constructor() { + //Bridge for crouton fix + this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge); + + this.exportAsync(FileManagerSource) // the catalog item currently being edited in the composer this.item = null; // the left and right sides of the canvas area @@ -99,6 +120,12 @@ class ComposerAppStore { this.showClassifiers = {}; this.editPathsMode = false; this.fullScreenMode = false; + this.panelTabShown = 'descriptor'; + //File manager values + this.files = []; + this.filesState = {}; + this.downloadJobs = {}; + //End File manager values this.bindListeners({ onResize: PanelResizeAction.RESIZE, editCatalogItem: CatalogItemsActions.EDIT_CATALOG_ITEM, @@ -124,8 +151,23 @@ class ComposerAppStore { openCanvasPanelTray: CanvasPanelTrayActions.OPEN, closeCanvasPanelTray: CanvasPanelTrayActions.CLOSE, enterFullScreenMode: ComposerAppActions.ENTER_FULL_SCREEN_MODE, - exitFullScreenMode: ComposerAppActions.EXIT_FULL_SCREEN_MODE + exitFullScreenMode: ComposerAppActions.EXIT_FULL_SCREEN_MODE, + showAssets: ComposerAppActions.showAssets, + showDescriptor: ComposerAppActions.showDescriptor, + getFilelistSuccess: FileManagerActions.getFilelistSuccess, + updateFileLocationInput: FileManagerActions.updateFileLocationInput, + sendDownloadFileRequst: FileManagerActions.sendDownloadFileRequst, + addFileSuccess: FileManagerActions.addFileSuccess, + deletePackageFile: FileManagerActions.deletePackageFile, + deleteFileSuccess: FileManagerActions.deleteFileSuccess, + closeFileManagerSockets: FileManagerActions.closeFileManagerSockets, + openFileManagerSockets: FileManagerActions.openFileManagerSockets, + openDownloadMonitoringSocketSuccess: FileManagerActions.openDownloadMonitoringSocketSuccess, + getFilelistSocketSuccess: FileManagerActions.getFilelistSocketSuccess }); + this.exportPublicMethods({ + closeFileManagerSockets: this.closeFileManagerSockets.bind(this) + }) } onResize(e) { @@ -166,6 +208,7 @@ class ComposerAppStore { } editCatalogItem(item) { + let self = this; if (item && item.uiState) { item.uiState.isOpenForEdit = true; if (item.uiState.type !== 'nsd') { @@ -174,8 +217,8 @@ class ComposerAppStore { } SelectionManager.select(item); this.updateItem(item); + this.openFileManagerSockets(item) } - catalogItemMetaDataChanged(item) { this.updateItem(item); } @@ -193,7 +236,8 @@ class ComposerAppStore { } showError(data) { - this.setState({message: data.errorMessage, messageType: 'error'}); + NotificationError.defer({msg: data.errorMessage, type: 'error'}) + // this.setState({message: data.errorMessage, messageType: 'error'}); } clearError() { @@ -396,7 +440,210 @@ class ComposerAppStore { this.setState({fullScreenMode: false}); } + showAssets() { + this.setState({ + panelTabShown: 'assets' + }); + } + showDescriptor() { + this.setState({ + panelTabShown: 'descriptor' + }); + } + + //File Manager methods + getFilelistSuccess(data) { + let self = this; + let filesState = null; + if (self.fileMonitoringSocketID) { + filesState = addInputState( _.cloneDeep(this.filesState),data); + // filesState = _.merge(self.filesState, addInputState({},data)); + let normalizedData = normalizeTree(data); + this.setState({ + files: { + data: _.mergeWith(normalizedData.data, self.files.data, function(obj, src) { + return _.uniqBy(obj? obj.concat(src) : src, 'name'); + }), + id: self.files.id || normalizedData.id + }, + filesState: filesState + }); + } + function normalizeTree(data) { + let f = { + id:[], + data:{} + }; + data.contents.map(getContents); + function getContents(d) { + if(d.hasOwnProperty('contents')) { + let contents = []; + d.contents.map(function(c,i) { + if (!c.hasOwnProperty('contents')) { + contents.push(c); + } else { + getContents(c); + } + }) + f.id.push(d.name); + f.data[d.name] = contents; + } + } + return f; + } + function addInputState(obj, d) { + d.newFile = ''; + if(d.hasOwnProperty('contents')) { + d.contents.map(addInputState.bind(null, obj)) + } + if(!obj[d.name]) { + obj[d.name] = ''; + } + return obj; + } + } + sendDownloadFileRequst(data) { + let id = data.id || this.item.id; + let type = data.type || this.item.uiState.type; + let path = data.path; + let url = data.url; + this.getInstance().addFile(id, type, path, url); + } + updateFileLocationInput = (data) => { + let name = data.name; + let value = data.value; + var filesState = _.cloneDeep(this.filesState); + filesState[name] = value; + this.setState({ + filesState: filesState + }); + } + addFileSuccess = (data) => { + let path = data.path; + let fileName = data.fileName; + let files = _.cloneDeep(this.files); + let loadingIndex = files.data[path].push({ + status: 'DOWNLOADING', + name: path + '/' + fileName + }) - 1; + this.setState({files: files}); + + } + startWatchingJob = () => { + let ws = window.multiplexer.channel(this.jobSocketId); + this.setState({ + jobSocket:null + }) + } + openDownloadMonitoringSocketSuccess = (id) => { + let self = this; + let ws = window.multiplexer.channel(id); + let downloadJobs = _.cloneDeep(self.downloadJobs); + let newFiles = {}; + ws.onmessage = (socket) => { + if (self.files && self.files.length > 0) { + let jobs = []; + try { + jobs = JSON.parse(socket.data); + } catch(e) {} + newFiles = _.cloneDeep(self.files); + jobs.map(function(j) { + //check if not in completed state + let fullPath = j['package-path']; + let path = fullPath.split('/'); + let fileName = path.pop(); + path = path.join('/'); + let index = _.findIndex(self.files.data[path], function(o){ + return fullPath == o.name + }); + if((index > -1) && newFiles.data[path][index]) { + newFiles.data[path][index].status = j.status + } else { + if(j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') { + newFiles.data[path].push({ + status: j.status, + name: fullPath + }) + } else { + // if () + } + } + }) + self.setState({ + files: newFiles + }) + // console.log(JSON.parse(socket.data)); + } + } + this.setState({ + jobSocketId: id, + jobSocket: ws + }) + + } + getFilelistSocketSuccess = (id) => { + let self = this; + let ws = window.multiplexer.channel(id); + ws.onmessage = (socket) => { + if (self.fileMonitoringSocketID) { + let data = []; + try { + data = JSON.parse(socket.data); + } catch(e) {} + self.getFilelistSuccess(data) + } + } + this.setState({ + fileMonitoringSocketID: id, + fileMonitoringSocket: ws + }) + + } + closeFileManagerSockets() { + this.fileMonitoringSocketID = null; + this.setState({ + jobSocketId : null, + fileMonitoringSocketID : null + // jobSocket : null, + // fileMonitoringSocket : null, + }); + this.jobSocket && this.jobSocket.close(); + this.fileMonitoringSocket && this.fileMonitoringSocket.close(); + console.log('closing'); + } + openFileManagerSockets(i) { + let self = this; + let item = i || self.item; + this.files = {data:[]}; + // this.closeFileManagerSockets(); + this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function() { + // // self.getInstance().openDownloadMonitoringSocket(item.id); + }); + this.getInstance().openDownloadMonitoringSocket(item.id); + } + endWatchingJob(id) { + + } + deletePackageFile(name) { + let id = this.item.id; + let type = this.item.uiState.type; + this.getInstance().deleteFile(id, type, name); + } + deleteFileSuccess = (data) => { + let path = data.path.split('/') + let files = _.cloneDeep(this.files); + path.pop(); + path = path.join('/'); + let pathFiles = files.data[path] + _.remove(pathFiles, function(c) { + return c.name == data.path; + }); + + this.setState({ + files: files + }) + } } export default alt.createStore(ComposerAppStore, 'ComposerAppStore'); diff --git a/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss b/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss index 4b9f15382..9fa11499c 100644 --- a/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss +++ b/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,5 +45,23 @@ .CanvasPanelBody { position: absolute; bottom: 0; + top: 142px; + } + .CanvasPanelTabs { + height: 32px; + margin-top: 49px; + background-color: #cbd1d1; + .CatalogFilter { + max-width:25%; + margin-left: 1rem; + margin-top: 3px; + button { + -ms-flex-pack: center; + justify-content: center; + &.-selected { + background:#e5e5e5; + } + } + } } } diff --git a/skyquake/plugins/composer/src/src/styles/ComposerAppToolbar.scss b/skyquake/plugins/composer/src/src/styles/ComposerAppToolbar.scss index 7b2ef1694..423e09e9a 100644 --- a/skyquake/plugins/composer/src/src/styles/ComposerAppToolbar.scss +++ b/skyquake/plugins/composer/src/src/styles/ComposerAppToolbar.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,17 +20,25 @@ @import 'main'; .ComposerAppToolbar { - @extend .panel-header; - top: 56px; - left: 300px; + @extend .panel-header; + top: 56px; + left: 300px; right: 0; - height: 55px; - z-index: 4; + height: 55px; + z-index: 4; position: absolute; padding: 10px 20px; background-color: $panel-bg-color-contrast; - white-space: nowrap; - div { - display: inline-block; - } + white-space: nowrap; + div { + display: inline-block; + } + .disableOverlay { + background-color: #cbd1d1; + width: 100%; + height: 40px; + opacity: 0.8; + position: absolute; + pointer-events: none; + } } diff --git a/skyquake/plugins/composer/src/src/styles/_main.scss b/skyquake/plugins/composer/src/src/styles/_main.scss index 15ff765c2..e8cee07b9 100644 --- a/skyquake/plugins/composer/src/src/styles/_main.scss +++ b/skyquake/plugins/composer/src/src/styles/_main.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,9 @@ @import 'variables'; path[data-outline-indicator-svg-outline-path] { - stroke-dasharray: 0; - stroke: #b58900; - stroke-width: 5px; + stroke-dasharray: 0; + stroke: #b58900; + stroke-width: 5px; } $font-path: '../assets'; @@ -161,49 +161,45 @@ $font-family-icons: 'FontAwesome'; $font-family-default: $font-family-sans-serif; html, body { - background: $primary-bg-color; - cursor: auto; - font-family: $font-family-sans-serif; - overflow: hidden; + background: $primary-bg-color; + cursor: auto; + font-family: $font-family-sans-serif; + overflow: hidden; } ::-webkit-scrollbar { - //display: none; + /*display: none;*/ } html { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } * { - margin: 0; - border: 0; - padding: 0; - border-collapse: collapse; + margin: 0; + border: 0; + padding: 0; + border-collapse: collapse; } *, *:before, *:after { - -webkit-box-sizing: inherit; - -moz-box-sizing: inherit; - box-sizing: inherit; + box-sizing: inherit; } h1 { - margin: 0; - padding: 3px 6px; - color: #323232; - font-size: 0.75em; - font-weight: normal; - text-align: center; - font-variant-caps: all-small-caps; - font-variant: small-caps; - text-transform: uppercase; + margin: 0; + padding: 3px 6px; + color: #323232; + font-size: 0.75em; + font-weight: normal; + text-align: center; + font-variant-caps: all-small-caps; + font-variant: small-caps; + text-transform: uppercase; } img { - vertical-align: middle; + vertical-align: middle; } button, @@ -217,239 +213,246 @@ input[type="image"] { text-decoration: none; text-transform: uppercase; - &:hover { - background-color: #7E9BC1; - color:#ffffff; - } - &:active, &:visited{ - background-color:#4C5E74; - color:#ffffff; - } + &:hover { + background-color: #7E9BC1; + color:#ffffff; + } + &:active, &:visited{ + background-color:#4C5E74; + color:#ffffff; + } - &.-selected { - background-color: rgb(159, 196, 244); - } + &.-selected { + background-color: rgb(159, 196, 244); + } } input[type="image"] { - width: 28px; - height: 28px; - margin-right: 12px; - border: 0; - border-radius: 4px; - padding: 4px; + width: 28px; + height: 28px; + margin-right: 12px; + border: 0; + border-radius: 4px; + padding: 4px; } input, select, textarea { - height: 25px; - line-height: 25px; - max-width: 100%; - min-width: 100%; - margin: 0; - padding: 0 8px; - border: 1px solid $field-background-color !important;; - border-radius: $field-border-radius; - color: #002b36; - background-color: $field-background-color !important; - vertical-align: top; - &:focus { - color: #002b36; - background-color: white !important; - } - &::-webkit-input-placeholder { - color: #eee8d5 !important; - } - - &:-moz-placeholder { /* Firefox 18- */ - color: #eee8d5 !important; - } - - &::-moz-placeholder { /* Firefox 19+ */ - color: #eee8d5 !important; - } - - &:-ms-input-placeholder { - color: #eee8d5 !important; - } + height: 25px; + line-height: 25px; + max-width: 100%; + min-width: 100%; + margin: 0; + padding: 0 8px; + border: 1px solid $field-background-color !important;; + border-radius: $field-border-radius; + color: #002b36; + background-color: $field-background-color !important; + vertical-align: top; + &:focus { + color: #002b36; + background-color: white !important; + } + &::-webkit-input-placeholder { + color: #eee8d5 !important; + } + + &:-moz-placeholder { /* Firefox 18- */ + color: #eee8d5 !important; + } + + &::-moz-placeholder { /* Firefox 19+ */ + color: #eee8d5 !important; + } + + &:-ms-input-placeholder { + color: #eee8d5 !important; + } } select { - padding-right: 0; - margin-right: 0; - -moz-appearance: none; - text-indent: 0.01px; - text-overflow: ''; - -webkit-appearance: none; - -webkit-border-radius: $field-border-radius; - &.-value-not-set { - color: #eee8d5; - } + padding-right: 0; + margin-right: 0; + -moz-appearance: none; + text-indent: 0.01px; + text-overflow: ''; + -webkit-appearance: none; + -webkit-border-radius: $field-border-radius; + &.-value-not-set { + color: #eee8d5; + } } select { - appearance: none; // using -prefix-free http://leaverou.github.io/prefixfree/ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; /* using -prefix-free http://leaverou.github.io/prefixfree/*/ background: $field-background-color url(../../../node_modules/open-iconic/svg/caret-bottom.svg) no-repeat right 6px center; - background-size: 10px; - border: { - color: $field-background-color; - radius: $field-border-radius; - style: solid; - width: 1px; - } -} - -// Removes default arrow for IE10+ -// IE 8/9 get dafault arrow which covers caret image -// as long as caret image is small than and positioned -// behind default arrow + background-size: 10px; + border: { + color: $field-background-color; + radius: $field-border-radius; + style: solid; + width: 1px; + } +} + +/* Removes default arrow for IE10+*/ +/* IE 8/9 get dafault arrow which covers caret image*/ +/* as long as caret image is small than and positioned*/ +/* behind default arrow*/ select::-ms-expand { - display: none; + display: none; } textarea { - height: 50px; + height: 50px; } input[name$="id"], input.-is-guid { - font-size: 10px; - font-family: monospace; + font-size: 10px; + font-family: monospace; } .ContentEditableDiv { - margin: 0; - border: 0; - padding: 0; - overflow: hidden; - border-radius: $field-border-radius; - height: 25px; - line-height: 25px; + margin: 0; + border: 0; + padding: 0; + overflow: hidden; + border-radius: $field-border-radius; + height: 25px; + line-height: 25px; } .btn-group { - white-space: nowrap; - button { - border-right: 1px solid $button-border-color; - &:first-of-type { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - } - &:last-of-type { - border-right: none; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - } - } + white-space: nowrap; + button { + border-right: 1px solid $button-border-color; + &:first-of-type { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } + &:last-of-type { + border-right: none; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + } + } } .panel { - overflow: hidden; - -ms-overflow-style: -ms-autohiding-scrollbar; - background-color: $panel-bg-color; - > div { - min-width: 200px; - } + overflow: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + background-color: $panel-bg-color; + > div { + min-width: 200px; + } } .panel-header { - h1 { - padding: 16px; - white-space: nowrap; - text-align: center; - } + h1 { + padding: 16px; + white-space: nowrap; + text-align: center; + } } .panel-body { - position: absolute; - overflow: hidden; - &:hover { - overflow: auto; - } - -ms-overflow-style: -ms-autohiding-scrollbar; - top: 111px; - bottom: 0px; - left: 0; - right: 0; + position: absolute; + overflow: hidden; + &:hover { + overflow: auto; + } + -ms-overflow-style: -ms-autohiding-scrollbar; + top: 111px; + bottom: 0px; + left: 0; + right: 0; } .panel-footer { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 40px; - padding: 0 12px; - background-color: $panel-bg-color-contrast; - border-top: 1px solid $panel-border-color-light; - white-space: nowrap; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + padding: 0 12px; + background-color: $panel-bg-color-contrast; + border-top: 1px solid $panel-border-color-light; + white-space: nowrap; } .welcome-message { - margin: 20px; - color: #06173c; - font-size: x-large; - font-weight: 200; - .Button { - display: inline-block; - } + margin: 20px; + color: #06173c; + font-size: x-large; + font-weight: 200; + .Button { + display: inline-block; + } } /* react-tooltip overrides */ .__react_component_tooltip { - transition: opacity 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000) !important; + transition: opacity 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000) !important; } /* simple css-only drop-down menu */ .menu { - display: inline-flex; - position: relative; - z-index: 9999; - > .Button:after { - content: "ˇ"; - position: absolute; - right: 4px; - bottom: -13px; - font-size: 20px; - } - .sub-menu { - display: none; - position: absolute; - top: 36px; - left: -4px; - padding: 4px; - background-color: #f1f1f1; - box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); - border-radius: 3px; - .Button { - display: block; - text-align: left; - } - &:hover { - display: block; - } - } - > .Button:hover .sub-menu { - display: block; - } - &:hover .sub-menu { - display: block; - } + display: -ms-inline-flexbox; + display: inline-flex; + position: relative; + z-index: 9999; + > .Button:after { + content: "ˇ"; + position: absolute; + right: 4px; + bottom: -13px; + font-size: 20px; + } + .sub-menu { + display: none; + position: absolute; + top: 36px; + left: -4px; + padding: 4px; + background-color: #f1f1f1; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); + border-radius: 3px; + .Button { + display: block; + text-align: left; + } + &:hover { + display: block; + } + } + > .Button:hover .sub-menu { + display: block; + } + &:hover .sub-menu { + display: block; + } } .descriptor { - text { - &.badge { - font-weight: 100; - font-size: 12px; - text-anchor: middle; - fill: white;/* font color */ - stroke: white !important;/* font color */ - } - } + text { + &.badge { + font-weight: 100; + font-size: 12px; + text-anchor: middle; + fill: white;/* font color */ + stroke: white !important;/* font color */ + } + } } .hint { - margin: 5px; + margin: 5px; +} + +.FileManager { + margin-top:1rem; }