diff --git a/webapp/controllers/administration/Storage.js b/webapp/controllers/administration/Storage.js new file mode 100644 index 000000000..da498a213 --- /dev/null +++ b/webapp/controllers/administration/Storage.js @@ -0,0 +1,40 @@ +'use strict'; + +// dependencies +const Enums = require('./../../core/Enums'); +const StorageFacade = require('./../../core/facade/storage'); +const makeTokenParameters = require('../../core/Utils').makeTokenParameters; + +/** + * It exports a object with Storage controllers (get/new/edit) + * @return {Object} A object with controllers with http method as key (get/new/edit) + */ +module.exports = function(app) { + return { + get: function(request, response) { + const parameters = makeTokenParameters(request.query.token, app); + const hasProjectPermission = request.session.activeProject.hasProjectPermission; + parameters.hasProjectPermission = hasProjectPermission; + response.render('administration/storages', Object.assign({}, parameters, {"Enums": Enums})); + }, + + new: function(request, response) { + response.render('administration/storage'); + }, + + edit: async (request, response) => { + const storageId = request.params.id; + const hasProjectPermission = request.session.activeProject.hasProjectPermission; + + const facade = new StorageFacade(); + + try { + await facade.get(storageId); + + response.render('administration/storage', {hasProjectPermission: hasProjectPermission, storage: storageId }); + } catch (err) { + response.render('base/404'); + } + } + }; +}; diff --git a/webapp/controllers/api/Service.js b/webapp/controllers/api/Service.js index 4a645fbe0..d27dfafc1 100644 --- a/webapp/controllers/api/Service.js +++ b/webapp/controllers/api/Service.js @@ -9,20 +9,25 @@ module.exports = function(app) { return { get: function(request, response) { var type = request.query.type; - var serviceId = request.query.serviceId; - + if (!serviceId) { + var serviceId = request.query.serviceId; //todo: improve it var restriction = {}; - switch (type) { - case "COLLECT": - restriction = {service_type_id: 1}; - break; - case "ANALYSIS": - restriction = {service_type_id: 2}; - break; - default: - break; + + if (isNaN(type)) { + switch (type) { + case "COLLECT": + restriction = {service_type_id: 1}; + break; + case "ANALYSIS": + restriction = {service_type_id: 2}; + break; + default: + break; + } + } else { + restriction.service_type_id = type; } return DataManager.listServiceInstances(restriction).then(function(services) { diff --git a/webapp/controllers/api/storage.js b/webapp/controllers/api/storage.js new file mode 100644 index 000000000..8de98dcc4 --- /dev/null +++ b/webapp/controllers/api/storage.js @@ -0,0 +1,68 @@ +// dependencies +const storageFacade = require('../../core/facade/storage'); + +const mergeStorageWithProject = (request, storage) => ( + { project_id: request.session.activeProject.id, ...storage } +); + +/** + * It exports a object with Storage controllers (get/new/edit) + * @return {Object} A object with controllers with http method as key (get/new/edit) + */ +module.exports = function(app) { + return { + get: async (request, response) => { + try { + let output = null; + + const facade = new storageFacade(); + + if (request.params.id) { + output = await facade.get(request.params.id); + } else { + output = await facade.list(); + } + + response.json(output); + } catch (err) { + response.status(400); + response.json({ error: err.message }); + } + }, + save: async (request, response) => { + try { + const storage = await new storageFacade().save(mergeStorageWithProject(request, request.body)); + response.json(storage); + } catch (err) { + response.status(err.code); + response.json(err.getErrors()); + } + }, + put: async (request, response) => { + try { + const facade = new storageFacade(); + + await facade.update(request.params.id, mergeStorageWithProject(request, request.body)); + + response.status(204); + response.json({}); + } catch (err) { + response.status(err.code); + response.json(err.getErrors()); + } + }, + delete: async (request, response) => { + try { + const facade = new storageFacade(); + + await facade.delete(request.params.id); + + response.status(204) + response.json({}); + } catch (err) { + response.status(err.code); + response.json(err.getErrors()); + } + } + }; +}; diff --git a/webapp/controllers/configuration/Status.js b/webapp/controllers/configuration/Status.js index 23ed43ff9..9d6147163 100644 --- a/webapp/controllers/configuration/Status.js +++ b/webapp/controllers/configuration/Status.js @@ -15,10 +15,11 @@ module.exports = function(app) { DataManager.listViews(), DataManager.listAlerts(), DataManager.listInterpolators(), + DataManager.listStorages(), DataManager.listProjects() ]) - .spread(function(collectors, analysisList, views, alerts, interpolators, projects) { + .spread(function(collectors, analysisList, views, alerts, interpolators, storages, projects) { var outputAnalysis = []; analysisList.forEach(function(analysis) { outputAnalysis.push(analysis.rawObject()); @@ -41,6 +42,10 @@ module.exports = function(app) { return interpolator.rawObject(); }); + var outputStorages = storages.map(function(storage){ + return storage; + }); + var renderParams = { "Enums": Enums, "analysis": outputAnalysis, @@ -48,6 +53,7 @@ module.exports = function(app) { "views": outputViews, "alerts": outputAlerts, "interpolators": outputInterpolators, + "storages": outputStorages, "projects": projects, "parameters": null }; diff --git a/webapp/controllers/configuration/Storages.js b/webapp/controllers/configuration/Storages.js new file mode 100644 index 000000000..a662b8eab --- /dev/null +++ b/webapp/controllers/configuration/Storages.js @@ -0,0 +1,28 @@ +"use strict"; + +var DataManager = require("./../../core/DataManager"); +var ScheduleType = require("./../../core/Enums").ScheduleType; +var makeTokenParameters = require('../../core/Utils').makeTokenParameters; + +module.exports = function(app) { + return { + get: function(request, response) { + var parameters = makeTokenParameters(request.query.token, app); + var hasProjectPermission = request.session.activeProject.hasProjectPermission; + parameters.hasProjectPermission = hasProjectPermission; + response.render("configuration/storages", parameters); + }, + new: function(request, response) { + return response.render("configuration/storages", {ScheduleType: ScheduleType}); + }, + edit: function(request, response) { + var hasProjectPermission = request.session.activeProject.hasProjectPermission; + DataManager.getStorage({id: parseInt(request.params.id)}) + .then(function(storage) { + return response.render("configuration/storages", {storage: storage.rawObject(), ScheduleType: ScheduleType, hasProjectPermission: hasProjectPermission}); + }).catch(function(err) { + return response.render("base/404"); + }); + } + } +}; diff --git a/webapp/core/Application.js b/webapp/core/Application.js index 12ebad577..17be4fc0d 100644 --- a/webapp/core/Application.js +++ b/webapp/core/Application.js @@ -1,6 +1,7 @@ var fs = require("fs"); var path = require("path"); var bcrypt = require('bcrypt'); +var util = require('util'); /** * It defines a cache object (private) with TerraMA² settings diff --git a/webapp/core/DataManager.js b/webapp/core/DataManager.js index c2b0fa792..fcb095912 100644 --- a/webapp/core/DataManager.js +++ b/webapp/core/DataManager.js @@ -105,6 +105,7 @@ var DataManager = module.exports = { init: function(dbConfigPath, callback) { var self = this; logger.info("Initializing database..."); + logger.info(dbConfigPath); return Database.init(dbConfigPath).then(function(dbORM) { logger.info("Database loaded."); @@ -139,6 +140,7 @@ var DataManager = module.exports = { inserts.push(models.db.ServiceType.create({id: Enums.ServiceType.VIEW, name: "VIEW"})); inserts.push(models.db.ServiceType.create({id: Enums.ServiceType.ALERT, name: "ALERT"})); inserts.push(models.db.ServiceType.create({id: Enums.ServiceType.INTERPOLATION, name: "INTERPOLATION"})); + inserts.push(models.db.ServiceType.create({id: Enums.ServiceType.STORAGE, name: "STORAGE"})); // data provider type defaults inserts.push(self.addDataProviderType({id: 1, name: "FILE", description: "Desc File"})); @@ -214,11 +216,19 @@ var DataManager = module.exports = { interpolationService.port = 6547; interpolationService.service_type_id = Enums.ServiceType.INTERPOLATION; + var storageService = Object.assign({}, collectorService); + storageService.name = "Local Storage"; + storageService.description = "Local service for Storage"; + storageService.pathToBinary = "../storage/storage_service.js", + storageService.port = 5000; + storageService.service_type_id = Enums.ServiceType.STORAGE; + servicesInsert.push(self.addServiceInstance(collectorService)); servicesInsert.push(self.addServiceInstance(analysisService)); servicesInsert.push(self.addServiceInstance(viewService)); servicesInsert.push(self.addServiceInstance(alertService)); servicesInsert.push(self.addServiceInstance(interpolationService)); + servicesInsert.push(self.addServiceInstance(storageService)); } return Promise.all(servicesInsert); }); @@ -3231,7 +3241,7 @@ var DataManager = module.exports = { return reject(new exceptions.CollectorError("Could not find collector. " + err.toString())); }); } else { - logger.error("Retrieved null while getting collector", collectorResult); + logger.error("Retrieved null while getting collector", restriction, collectorResult); return reject(new exceptions.CollectorErrorNotFound("Could not find collector. ")); } }).catch(function(err) { @@ -6629,5 +6639,66 @@ var DataManager = module.exports = { return reject(err); }); }); - } + }, + + /** + * It retrieves all storages from database + * + * @param {Object} restriction - A query restriction + * @param {Object} options - A query options + * @param {Transaction} options.transaction - An ORM transaction + * @returns {Promise>} + */ + listStorages: function(restriction, options) { + var self = this; + return new Promise(function(resolve, reject) { + var _reject = function(err) { + logger.error(err); + reject(err); + }; + + return models.db.Storages.findAll({}) + .then(function(storages){ + var output = []; + storages.forEach(function(storage) { + output.push(storage.get()); + }); + resolve(output); + }).catch(function(err) { + reject(new Error("Could not retrieve storage " + err.toString())); + }); + + }); + }, + + /** + * It retrieves a storage of database from given restriction + * + * @param {Object} restriction - A query restriction + * @param {Object} options - A query options + * @param {Transaction} options.transaction - An ORM transaction + * @returns {Promise} + */ + getStorage: function(restriction, options) { + var self = this; + return new Promise(function(resolve, reject) { + self.listStorages(restriction, options) + .then(function(storages) { + if (storages.length === 0) { + return reject(new Error("No storage retrieved")); + } + + if (storages.length > 1) { + return reject(new Error("Get operation retrieved more than a storage")); + } + + return resolve(storages[0]); + }) + .catch(function(err) { + return reject(err); + }); + }); + }, + }; + diff --git a/webapp/core/Enums.js b/webapp/core/Enums.js index dc70be8e5..292967b12 100644 --- a/webapp/core/Enums.js +++ b/webapp/core/Enums.js @@ -134,7 +134,12 @@ module.exports = { * Defines Interpolation Service * @type {number} */ - INTERPOLATION: 5 + INTERPOLATION: 5, + /** + * Defines Storage Service + * @type {number} + */ + STORAGE: 6 }, DataSeriesType: { diff --git a/webapp/core/Exceptions.js b/webapp/core/Exceptions.js index 0b87b96e2..516813622 100644 --- a/webapp/core/Exceptions.js +++ b/webapp/core/Exceptions.js @@ -406,3 +406,20 @@ errors.InterpolatorError = function(message) { this.name = 'InterpolatorError'; }; util.inherits(errors.InterpolatorError, errors.BaseError); + +class TcpError extends Error { + constructor(service, message, process = null) { + super(message); + + this.service = service; + this.process = process; + } +} +class NoSuchConnectionError extends TcpError { + constructor(service, process = null) { + super(service, `There is no active connection to the service ${service.name || service.id || service}`, process); + } +} + +errors.TcpError = TcpError; +errors.NoSuchConnectionError = NoSuchConnectionError; \ No newline at end of file diff --git a/webapp/core/Logger.js b/webapp/core/Logger.js index 0bf5c671e..44b3ae1b6 100644 --- a/webapp/core/Logger.js +++ b/webapp/core/Logger.js @@ -59,6 +59,7 @@ * @returns {string} Formatted string to log */ function loggerFormat(args) { + format = new Date().toLocaleTimeString(); return NodeFormat("[%s] %s %s", format, winston.config.colorize(args.level), args.message); } diff --git a/webapp/core/ObjectDependencies.js b/webapp/core/ObjectDependencies.js index 667f78b02..69e63d358 100644 --- a/webapp/core/ObjectDependencies.js +++ b/webapp/core/ObjectDependencies.js @@ -211,6 +211,21 @@ var ObjectDependencies = function(json){ ); } } + else if (json.objectType == "Storage"){ + for(var i = 0, idsLength = json.ids.length; i < idsLength; i++) { + promises.push( + DataManager.getStorage({id: json.ids[i]}).then(function(storage){ + if(output["Storages_" + storage.id] === undefined) output["Storages_" + storage.id] = {}; + + if(output["Storages_" + storage.id] === undefined) output["Storages_" + storage.id] = {}; + var storageDataseriesListPromises = [getDataSeriesDependencies(storage.data_series_input, null, "Storages_" + storage.id)]; + storageDataseriesListPromises.push(getDataSeriesDependencies(storage.data_series_output, null, "Storages_" + storage.id)); + + return Promise.all(storageDataseriesListPromises).catch(_emitError); + }) + ); + } +} return Promise.all(promises).then(function() { return Promise.resolve({ status: 200, data: output, projectId: json.projectId }); diff --git a/webapp/core/Process.js b/webapp/core/Process.js index 0f001335e..fe04a9db5 100644 --- a/webapp/core/Process.js +++ b/webapp/core/Process.js @@ -49,14 +49,18 @@ Process.prototype.startService = function(command) { var port = serviceInstance.port.toString(); var serviceTypeString = Utils.getServiceTypeName(serviceInstance.service_type_id); - if(config.disablePDF && !config.debug) { - var command = util.format("%s %s %s %s %s %s %s %s %s", "sudo", "-H", "-u", "terrama2", executable, serviceTypeString, port, '-platform', 'minimal'); - } else if(!config.disablePDF && !config.debug) { - var command = util.format("%s %s %s %s %s %s %s", "sudo", "-H", "-u", "terrama2", executable, serviceTypeString, port); - } else if(config.disablePDF && config.debug) { - var command = util.format("%s %s %s %s %s", executable, serviceTypeString, port, '-platform', 'minimal'); - } else { - var command = util.format("%s %s %s", executable, serviceTypeString, port); + if (serviceTypeString === "STORAGE") + var command = util.format("node %s \"\" %s", executable, port); + else{ + if(config.disablePDF && !config.debug) { + var command = util.format("%s %s %s %s %s %s %s %s %s", "sudo", "-H", "-u", "terrama2", executable, serviceTypeString, port, '-platform', 'minimal'); + } else if(!config.disablePDF && !config.debug) { + var command = util.format("%s %s %s %s %s %s %s", "sudo", "-H", "-u", "terrama2", executable, serviceTypeString, port); + } else if(config.disablePDF && config.debug) { + var command = util.format("%s %s %s %s %s", executable, serviceTypeString, port, '-platform', 'minimal'); + } else { + var command = util.format("%s %s %s", executable, serviceTypeString, port); + } } return self.adapter.startService(command) diff --git a/webapp/core/Service.js b/webapp/core/Service.js index 901b8086c..2090e6d05 100644 --- a/webapp/core/Service.js +++ b/webapp/core/Service.js @@ -123,10 +123,14 @@ var Service = module.exports = function(serviceInstance) { self.answered = true; var formatMessage = "Socket %s received %s"; logger.debug(Utils.format(formatMessage, self.service.name, byteArray)); + console.log("REceived byteArray.size ", byteArray.length) +// console.log("byteArray ", byteArray); // append and check if the complete message has arrived tempBuffer = _createBufferFrom(tempBuffer, byteArray); + // console.log("tempBuffer ", tempBuffer); + let completeMessage = true; // process all messages in the buffer // or until we get a incomplete message @@ -166,7 +170,6 @@ var Service = module.exports = function(serviceInstance) { tempBuffer = undefined; throw new Error("Invalid message (EOM)"); } - // if we got many messages at once // hold the buffer we the extra messages for processing if(tempBuffer.length > expectedLength+headerSize) { @@ -174,14 +177,13 @@ var Service = module.exports = function(serviceInstance) { } else { extraData = undefined; } - - // get only the first message for processing tempBuffer = new Buffer.from(tempBuffer.slice(beginOfMessage.length, expectedLength+beginOfMessage.length)); + // get only the first message for processing const parsed = parseByteArray(tempBuffer); // get next message in the buffer for processing tempBuffer = extraData; - + switch(parsed.signal) { case Signals.LOG_SIGNAL: self.emit("log", parsed.message); @@ -214,6 +216,7 @@ var Service = module.exports = function(serviceInstance) { // we got an error, empty buffer. tempBuffer = undefined; logger.debug(Utils.format("Error parsing bytearray received from %s. %s", self.service.name, e.toString())); + console.log(byteArray.toString()); self.emit("serviceError", e); if (callbackError) { callbackError(e); diff --git a/webapp/core/TcpManager.js b/webapp/core/TcpManager.js index 5bc99c58a..a59bf818e 100644 --- a/webapp/core/TcpManager.js +++ b/webapp/core/TcpManager.js @@ -13,10 +13,14 @@ var EventEmitter = require('events').EventEmitter; var ServiceType = require('./Enums').ServiceType; var PromiseClass = require('./Promise'); var Application = require('./Application'); +const { NoSuchConnectionError } = require('./Exceptions'); // Facades var ProcessFinished = require("./facade/tcp-manager/ProcessFinished"); +let beginOfMessage = "(BOM)\0"; +let endOfMessage = "(EOM)\0"; + /** * It handles entire TCP communication between TerraMA² NodeJS application and C++ Services. * Do not use it directly. Prefer to use TcpService.js @@ -93,6 +97,59 @@ TcpManager.prototype.makebuffer = function(signal, object) { // // Writes the signal (unsigned 32-bit integer) in the buffer with big endian format buffer.writeUInt32BE(signal, 4); + // console.log("TcpMAnager.makebuffer", buffer); + return buffer; + } catch (e) { + throw e; + } +}; + + +/** + This method prepares a byte array to send in tcp socket. + @param {Signals} signal - a valid TerraMA2 tcp signal + @param {Object} object - a javascript object message to send + */ +TcpManager.prototype.makebuffer_be = function(signal, object) { + try { + if(isNaN(signal)) { throw TypeError(signal + " is not a valid signal!"); } + + var totalSize; + var jsonMessage = ""; + + var hasMessage = !_.isEmpty(object); + + if (hasMessage) { + jsonMessage = JSON.stringify(object).replace(/\":/g, "\": "); + + // The size of the message plus the size of two integers, 4 bytes each + totalSize = jsonMessage.length + 4; + } else { totalSize = 4; } + + // creating buffer to store message + var bufferMessage = new Buffer(jsonMessage); + + // Creates the buffer to be sent + var buffer = new Buffer(bufferMessage.length + 8 + 12); + + if (hasMessage) { + // Writes the message (string) in the buffer with UTF-8 encoding + bufferMessage.copy(buffer, 14, 0, bufferMessage.length); + } + + // checking bufferMessage length. If it is bigger than jsonMessage, + // then there are special chars and the message size must be adjusted. + if (bufferMessage.length > jsonMessage.length) { totalSize = bufferMessage.length + 4; } + + // Writes the buffer size (unsigned 32-bit integer) in the buffer with big endian format + buffer.write(beginOfMessage, 0); + buffer.writeUInt32BE(totalSize, 6); + + // // Writes the signal (unsigned 32-bit integer) in the buffer with big endian format + buffer.writeUInt32BE(signal, 10); + buffer.write(endOfMessage, buffer.length - 6); + + // console.log("TcpMAnager.makebuffer_be", buffer); return buffer; } catch (e) { throw e; @@ -136,7 +193,8 @@ function _getClient(connection) { var logs = { collectors: [], analysis: [], - views: [] + views: [], + storages: [] }; /** @@ -152,10 +210,16 @@ TcpManager.prototype.$send = function(serviceInstance, data, signal) { var config = Application.getContextConfig(); data.webAppId = config.webAppId; - var buffer = this.makebuffer(signal, data); + + if (serviceInstance.service_type_id == ServiceType.STORAGE) + var buffer = this.makebuffer_be(signal, data); + else + var buffer = this.makebuffer(signal, data); + //logger.debug(buffer); logger.debug("BufferToString: ", buffer.toString()); logger.debug("BufferToString size: ", buffer.length); + logger.debug("Send Signal: ", signal, " serviceInstance: ", serviceInstance.name) client.send(buffer); } catch (e) { @@ -213,13 +277,17 @@ TcpManager.prototype.logData = function(serviceInstance, data) { // checking first attempt when there is no active socket (listing services) if (!client.isOpen()) { - self.emit('error', client.service, new Error("There is no active connection")); + self.emit('tcpError', client.service, new NoSuchConnectionError(client.service, data.process_ids)); return; } var config = Application.getContextConfig(); data.webAppId = config.webAppId; - var buffer = self.makebuffer(Signals.LOG_SIGNAL, data); + if (serviceInstance.service_type_id == ServiceType.STORAGE) + var buffer = self.makebuffer_be(Signals.LOG_SIGNAL, data); + else + var buffer = self.makebuffer(Signals.LOG_SIGNAL, data); + logger.debug("LOG_SIGNAL", buffer.toString()); // requesting for log client.log(buffer); @@ -265,8 +333,13 @@ TcpManager.prototype.startService = function(serviceInstance) { } return instance.connect(serviceInstance).then(function() { + // if (serviceInstance.service_type_id == ServiceType.STORAGE) + // var obj = ['', serviceInstance.port]; + // else + var obj = ['--version', '-platform', 'minimal']; + return PromiseClass.all([ - instance.adapter.execute(serviceInstance.pathToBinary, ['--version', '-platform', 'minimal'], {}), + instance.adapter.execute(serviceInstance.pathToBinary, obj, {}), instance.startService() ]).spread(function(versionResponse, startResponse) { self.emit("serviceVersion", serviceInstance, versionResponse.data); @@ -312,17 +385,21 @@ TcpManager.prototype.statusService = function(serviceInstance) { var self = this; try { var config = Application.getContextConfig(); - var buffer = self.makebuffer(Signals.STATUS_SIGNAL, {webAppId: config.webAppId}); + if (serviceInstance.service_type_id == ServiceType.STORAGE) + var buffer = self.makebuffer_be(Signals.STATUS_SIGNAL, {webAppId: config.webAppId}); + else + var buffer = self.makebuffer(Signals.STATUS_SIGNAL, {webAppId: config.webAppId}); //logger.debug(buffer); var client = _getClient(serviceInstance); // checking first attempt when there is no active socket (listing services) if (!client.isOpen()) { - self.emit('error', client.service, new Error("There is no active connection")); + self.emit('tcpError', client.service, new NoSuchConnectionError(client.service)); return; } + logger.debug("StatusService", buffer.toString()); client.status(buffer); } catch (e) { @@ -362,7 +439,10 @@ TcpManager.prototype.stopService = function(serviceInstance) { var self = this; try { var config = Application.getContextConfig(); - var buffer = self.makebuffer(Signals.TERMINATE_SERVICE_SIGNAL, {webAppId: config.webAppId}); + if (serviceInstance.service_type_id == ServiceType.STORAGE) + var buffer = self.makebuffer_be(Signals.TERMINATE_SERVICE_SIGNAL, {webAppId: config.webAppId}); + else + var buffer = self.makebuffer(Signals.TERMINATE_SERVICE_SIGNAL, {webAppId: config.webAppId}); var client = _getClient(serviceInstance); @@ -411,9 +491,12 @@ TcpManager.prototype.initialize = function(client) { case ServiceType.ANALYSIS: target = logs.analysis; break; - case ServiceType.VIEW: + case ServiceType.VIEW: target = logs.views; break; + case ServiceType.STORAGE: + target = logs.storages; + break; } if (target.length === 0) { @@ -452,7 +535,8 @@ TcpManager.prototype.initialize = function(client) { if (response.automatic !== false && (targetProcess.serviceType == ServiceType.COLLECTOR || targetProcess.serviceType == ServiceType.ANALYSIS - || targetProcess.serviceType == ServiceType.INTERPOLATION)){ + || targetProcess.serviceType == ServiceType.INTERPOLATION + || targetProcess.serviceType == ServiceType.STORAGE)){ targetProcess.processToRun.forEach(function(processToRun){ if (processToRun && processToRun.object && processToRun.object.active){ self.startProcess(processToRun.instance, {ids: processToRun.ids, execution_date: response.execution_date}); diff --git a/webapp/core/Utils.js b/webapp/core/Utils.js index ebcfbce41..72be4de50 100644 --- a/webapp/core/Utils.js +++ b/webapp/core/Utils.js @@ -296,17 +296,26 @@ var Utils = module.exports = { interpolators.forEach(function(interpolator){ interpolatorsArr.push(interpolator.toService()); }); - return resolve({ - "Projects": projects, - "Analysis": analysisArr, - "DataSeries": series, - "DataProviders": providers, - "Collectors": collectors, - "Views": viewsArr, - "Alerts": alertsArr, - "Legends": legendsArr, - "Interpolators": interpolatorsArr - }); + + DataManager.listStorages().then(function(storages){ + var storagesArr = []; + storages.forEach(function(storage){ + storagesArr.push(storage); + }); + + return resolve({ + "Projects": projects, + "Analysis": analysisArr, + "DataSeries": series, + "DataProviders": providers, + "Collectors": collectors, + "Views": viewsArr, + "Alerts": alertsArr, + "Legends": legendsArr, + "Interpolators": interpolatorsArr, + "Storages": storagesArr + }); + }).catch(_handleError); //end listStorages }).catch(_handleError); //end listInterpolators }).catch(_handleError); // end listLegends }).catch(_handleError); // end listAlerts @@ -485,6 +494,9 @@ var Utils = module.exports = { case Enums.ServiceType.INTERPOLATION: output = "INTERPOLATOR"; break; + case Enums.ServiceType.STORAGE: + output = "STORAGE"; + break; } if (output && output !== null) { return output; } diff --git a/webapp/core/data-model/Storage.js b/webapp/core/data-model/Storage.js new file mode 100644 index 000000000..97b6946bd --- /dev/null +++ b/webapp/core/data-model/Storage.js @@ -0,0 +1,83 @@ + +'use strict'; + + // Dependency + + /** + * TerraMA² AbstractClass of Data Model + * @type {AbstractData} + */ + var AbstractClass = require("./AbstractData"); + var Schedule = require("./Schedule"); + var AutomaticSchedule = require("./AutomaticSchedule"); +/** + * TerraMA² Storage representation + * + * @constructor + * @inherits AbstractData + */ +function Storage(params) { + + AbstractClass.call(this, {'class': 'Storage'}); + + this.id = params.id; + this.name= params.name, + this.description= params.description, + this.active = params.active; + this.erase_all = params.erase_all, + this.project_id = params.project_id; + this.filter = params.filter, + this.service_instance_id = params.service_instance_id; + this.data_series_id = params.data_series_id; + this.data_provider = params.data_provider; + this.schedule_type = params.schedule_type; + this.keep_data = params.keep_data, + this.keep_data_unit = params.keep_data_unit, + this.backup = params.backup, + this.uri = params.uri, + this.zip = params.zip + + if (params.Schedule || params.schedule){ + this.schedule = new Schedule(params.Schedule ? params.Schedule.get() : params.schedule); + } else { + this.schedule = {}; + } + + this.automaticSchedule = new AutomaticSchedule(params.AutomaticSchedule ? params.AutomaticSchedule.get() : params.automaticSchedule || {}); +}; + +// javascript inherits model +Storage.prototype = Object.create(AbstractClass.prototype); +Storage.prototype.constructor = Storage; + +Storage.prototype.toObject = function() { + return Object.assign(AbstractClass.prototype.toObject.call(this), { + id: this.id, + name: this.name, + description: this.description, + active: this.active, + erase_all: this.erase_all, + keep_data: this.keep_data, + keep_data_unit: this.keep_data_unit, + filter: this.filter, + backup: this.backup, + uri: this.uri, + zip: this.zip, + project_id: this.projectId, + dataSeries: this.dataSeries instanceof AbstractClass ? this.dataSeries.toObject() : {}, + dataProvider: this.dataProvider instanceof AbstractClass ? this.dataProvider.toObject() : {}, + schedule_type: this.scheschedule_typedule instanceof AbstractClass ? this.schedschedule_typeule.toObject() : {}, + automatic_schedule: this.automaticSchedule instanceof AbstractClass ? this.automaticSchedule.toObject() : {}, + service_instance_id: this.serviceInstanceId + }); +}; + +Storage.prototype.rawObject = function() { + var toObject = this.toObject(); + + toObject.dataSeries = this.dataSeries instanceof AbstractClass ? this.dataSeries.toObject() : this.dataSeries; + toObject.dataProvider = this.dataProvider instanceof AbstractClass ? this.dataProvider.toObject() : this.dataProvider; + return toObject; +}; + +module.exports = Storage; diff --git a/webapp/core/data-model/index.js b/webapp/core/data-model/index.js index f9f05ab6e..dfffee6b2 100644 --- a/webapp/core/data-model/index.js +++ b/webapp/core/data-model/index.js @@ -32,3 +32,4 @@ exports.Interpolator = require("./Interpolator"); exports.AlertAttachedView = require("./AlertAttachedView"); exports.AlertAttachment = require("./AlertAttachment"); exports.Project = require('./Project'); +exports.Storage = require('./Storage'); diff --git a/webapp/core/executors/Local.js b/webapp/core/executors/Local.js index 1dfe3a716..725badfbc 100644 --- a/webapp/core/executors/Local.js +++ b/webapp/core/executors/Local.js @@ -7,6 +7,7 @@ var execAsync = require("child_process").exec; var OS = require('./../Enums').OS; var ScreenAdapter = require('./adapters/ScreenAdapter'); var LocalSystemAdapter = require("./adapters/LocalSystemAdapter"); +var ServiceType = require("./../Enums").ServiceType; /** @@ -142,8 +143,8 @@ LocalExecutor.prototype.execute = function(command, commandArgs, options) { if (command === "uname") { defineAdapter(data.toString()); } - - responseMessage = data.toString(); + else if(command.search('storage_service') != -1) + responseMessage = data.toString(); }); // stream for handling errors data diff --git a/webapp/core/executors/adapters/ScreenAdapter.js b/webapp/core/executors/adapters/ScreenAdapter.js index ff5769597..39dd32b40 100644 --- a/webapp/core/executors/adapters/ScreenAdapter.js +++ b/webapp/core/executors/adapters/ScreenAdapter.js @@ -4,6 +4,7 @@ var util = require('util'); var logger = require("./../../Logger"); var Promise = require('./../../Promise'); +var ServiceType = require("./../../Enums").ServiceType; /** * It defines a generic interface to handle command executors diff --git a/webapp/core/facade/Project.js b/webapp/core/facade/Project.js index 3186a90f6..462238642 100644 --- a/webapp/core/facade/Project.js +++ b/webapp/core/facade/Project.js @@ -173,6 +173,7 @@ "DataSeries": [], "Legends": [], "Views": [], + "Storages": [], "Projects": [new ProjectModel(project).toObject()] }; @@ -219,6 +220,12 @@ objectToSend.Views.push(view.id); }); + return DataManager.listStorages({ project_id: projectId }); + }).then(function(storages) { + storages.forEach(function(storage) { + objectToSend.Storages.push(storage.id); + }); + return Promise.all(collectorPromises).catch(function(err) { if(!err.name || (err.name && err.name != "CollectorErrorNotFound")) return reject(new ProjectError("Failed to load dependents")); @@ -246,7 +253,10 @@ delete objectToSend.Legends; if(objectToSend.Views.length === 0) - delete objectToSend.Views; + delete objectToSend.Views; + + if(objectToSend.Storages.length === 0) + delete objectToSend.Storages; if( objectToSend.hasOwnProperty("Alerts") || @@ -255,7 +265,8 @@ objectToSend.hasOwnProperty("DataProvider") || objectToSend.hasOwnProperty("DataSeries") || objectToSend.hasOwnProperty("Legends") || - objectToSend.hasOwnProperty("Views") + objectToSend.hasOwnProperty("Views") || + objectToSend.hasOwnProperty("Storages") ) { TcpService.remove(objectToSend); } diff --git a/webapp/core/facade/storage.js b/webapp/core/facade/storage.js new file mode 100644 index 000000000..fb58d4868 --- /dev/null +++ b/webapp/core/facade/storage.js @@ -0,0 +1,186 @@ +const { ValidationErrorItem } = require('sequelize'); +const DataManager = require("./../DataManager"); +const URIBuilder = require('./../UriBuilder'); +const { ScheduleType, Uri } = require('./../Enums'); +const TcpService = require('./tcp-manager/TcpService'); + +class StorageError extends Error { + constructor(errors, code = 400) { + super('Storage Error'); + + this.errors = errors; + this.code = code; + } + + getErrors() { + const { errors } = this; + + const errorList = []; + + for(const error of errors) { + let formatedError = { }; + + if (error instanceof ValidationErrorItem) { + formatedError.field = error.path; + formatedError.message = error.message; + } else { + formatedError.field = error.message; + } + + errorList.push(formatedError); + } + + return errorList; + } +} + +class storageFacade { + /** + * Retrieves list of storages based in query restriction + * + * @param {any} restriction Query restriction. + */ + async list(restriction = {}){ + const storages = await DataManager.orm.models.Storages.findAll({ + ...restriction, + include: [ + { + model: DataManager.orm.models.Schedule + } + ] + }); + return storages.map(storage => { + const storageOut = storage.get(); + + if (storageOut.uri) + storageOut.uriObject = URIBuilder.buildObject(storageOut.uri, Uri); + + if (storage.Schedule) { + storageOut.schedule = storage.Schedule.get(); + storageOut.schedule.scheduleType = storage.schedule_type.toString(); + storageOut.schedule.scheduleHandler = storageOut.schedule.frequency_unit || storageOut.schedule.schedule_unit; + } + + return storageOut; + }); + } + + /** + * Find storage by id + * + * @param {number} id Storage identifier + */ + async get(id) { + const matched = await this.list({ where: { id }, limit: 1 }); + + return matched[0]; + } + + /** + * Persists the storager into database + * + * @throws {ScheduleError} for any error occurred in operation + * + * @param {any} storage Storage to save + */ + async save(storage){ + let transaction = await DataManager.orm.transaction(); + + try { + const options = { transaction }; + + if (storage.schedule && (storage.schedule.scheduleType !== "3")) { + const createdSchedule = await DataManager.addSchedule(storage.schedule, options); + + storage.schedule_id = createdSchedule.id; + } + + storage.schedule_type = storage.schedule.scheduleType; + + // Tries to create model Storage + const createdStorage = await DataManager.orm.models.Storages.create(storage, options); + + transaction.commit(); + + await TcpService.send({'Storages': [createdStorage]}); + + return createdStorage; + } catch (error) { + transaction.rollback(); + + throw new StorageError(error.errors); + } + } + + async update(storageId, storage) { + let transaction = await DataManager.orm.transaction(); + + try { + const options = { transaction }; + // Retrieves context storage + const oldStorage = await this.get(storageId); + + // We should define this variable since we cannot remove directly a schedule which belongs to storager + // due CASCADE operation. We must keep which schedule to remove and after Storage update + // perform operation. + let scheduleToRemove = null; + + // If there already schedule + if (oldStorage.schedule_id) { + // When no schedule sent, set storager schedule to none and keep id to remove after update sequency + if (storage.schedule.scheduleType == ScheduleType.MANUAL) { + scheduleToRemove = oldStorage.schedule_id; + storage.schedule_id = null; + } else + await DataManager.updateSchedule(oldStorage.schedule_id, storage.schedule, { transaction }); + } else { + if (storage.schedule && storage.schedule.scheduleType != ScheduleType.MANUAL) { + const createdSchedule = await DataManager.addSchedule(storage.schedule, options); + storage.schedule_type = storage.schedule.scheduleType; + + storage.schedule_id = createdSchedule.id; + } + } + // Update entire storage entity + await DataManager.orm.models.Storages.update(storage, { where: { id: storageId }, ...options }); + + // Remove schedule when updating to manual type + if (scheduleToRemove !== null) { + await DataManager.removeSchedule({ id: scheduleToRemove }, options); + } + + // Persists the operation + await transaction.commit(); + + const updatedStorage = await this.get(storageId); + + await TcpService.send({'Storages': [updatedStorage]}); + + return; + } catch (err) { + await transaction.rollback(); + + throw new StorageError(err.errors); + } + } + + async delete(id) { + let transaction = await DataManager.orm.transaction(); + + try { + const options = { transaction }; + + await DataManager.orm.models.Storages.destroy({ where: { id }, ...options }); + + // Persists the operation + await transaction.commit(); + + await TcpService.remove({'Storages': [id]}); + } catch (err) { + await transaction.rollback(); + + throw new StorageError(err.errors); + } + } +} +module.exports = storageFacade; diff --git a/webapp/core/facade/tcp-manager/ProcessFinished.js b/webapp/core/facade/tcp-manager/ProcessFinished.js index 47a5a9f7a..541c01d91 100644 --- a/webapp/core/facade/tcp-manager/ProcessFinished.js +++ b/webapp/core/facade/tcp-manager/ProcessFinished.js @@ -44,9 +44,12 @@ case ServiceType.VIEW: handler = self.handleRegisteredViews(response); break; - case ServiceType.ALERT: + case ServiceType.ALERT: handler = self.handleFinishedAlert(response); break; + case ServiceType.STORAGE: + handler = self.handleFinishedStorage(response); + break; default: throw new ServiceTypeError(Utils.format("Invalid instance id %s", response.instance_id)); } @@ -243,6 +246,35 @@ } }); } + + ProcessFinished.handleFinishedStorage = function(storageResultObject){ + return new PromiseClass(function(resolve, reject){ + if (storageResultObject.result){ + return DataManager.orm.transaction(function(t){ + var options = {transaction: t}; + // Get storage object that run + return DataManager.getStorage({id: storageResultObject.process_id}, options) + .then(function(storage){ + var dataSeriesId = storage.data_series_id; + var restritions = { + data_ids: { + $contains: [dataSeriesId] + } + }; + // return the process are conditioned by collector + return listConditionedProcess(restritions, options, resolve, reject); + }) + .catch(function(err){ + return reject(new Error(err.toString())); + }); + }); + } else { + return reject(new Error("The storage process finished with error")); + } + }); + } + + /** * Function to list conditioned process */ diff --git a/webapp/core/facade/tcp-manager/TcpService.js b/webapp/core/facade/tcp-manager/TcpService.js index 6cef08bd6..5e974fc9a 100644 --- a/webapp/core/facade/tcp-manager/TcpService.js +++ b/webapp/core/facade/tcp-manager/TcpService.js @@ -198,6 +198,7 @@ TcpService.prototype.start = async function(json) { try { instance = await DataManager.getServiceInstance({id: json.service}) // emitting service starting + console.log("TcpService.prototype.start " + instance.name); this.emit("serviceStarting", {service: instance.id}); // spreading all promises in order to retrieve service instance and promise result in two vars @@ -284,6 +285,7 @@ TcpService.prototype.run = async function(processObject) { } } + console.log("processRun", processObject); startProcess(instance, processObject); // Notify children listeners the process has been scheduled this.emit("processRun", processObject); @@ -312,10 +314,12 @@ TcpService.prototype.$sendStatus = function(service) { service: service.id }; // notify every one with loading + console.log("serviceRequestingStatus", params); self.emit("serviceRequestingStatus", params); return TcpManager.connect(service) .then(function() { + console.log("statusService", service); TcpManager.emit("statusService", service); return resolve(); }) @@ -323,6 +327,7 @@ TcpService.prototype.$sendStatus = function(service) { params.status = 400; params.checking = false; // notify every one + console.log("serviceRequestingStatus", params); self.emit("serviceRequestingStatus", params); return reject(err); }); @@ -335,6 +340,7 @@ TcpService.prototype.stopAll = function stopAll() { return DataManager.listServiceInstances() .then(function(instances) { instances.forEach(function(instance) { + console.log("serviceStopping", instance); self.emit("serviceStopping", instance); TcpManager.emit("stopService", instance); @@ -371,6 +377,11 @@ TcpService.prototype.status = function(json) { return resolve(); }).catch(function(err) { logger.debug(err); + console.log("serviceError", { + exception: err, + message: err.toString(), + service: json.service + }); self.emit("serviceError", { exception: err, message: err.toString(), @@ -471,6 +482,7 @@ TcpService.prototype.send = function(data, serviceId) { .then(function(services) { var _sendData = function(service) { try { + console.log('sendData', service, data); TcpManager.emit('sendData', service, data); } catch(err) { } }; @@ -554,10 +566,11 @@ TcpService.prototype.log = function(json) { DataManager.listCollectors(), DataManager.listViews(), DataManager.listAlerts(), - DataManager.listInterpolators() + DataManager.listInterpolators(), + DataManager.listStorages() ]) // spreading promiser result into services, analysisList, collectors and views variables - .spread(function(services, analysisList, collectors, views, alerts, interpolators) { + .spread(function(services, analysisList, collectors, views, alerts, interpolators, storages) { var obj = { begin: begin, end: end @@ -585,11 +598,15 @@ TcpService.prototype.log = function(json) { case ServiceType.INTERPOLATION: obj.process_ids = interpolators.map(function(elm) { return elm.id; }); break; + case ServiceType.STORAGE: + obj.process_ids = storages.map(function(elm) { return elm.id; }); + break; default: throw new Error("Invalid service type"); } // requesting log data + console.log("logData", service, obj); TcpManager.emit("logData", service, obj); }); // end foreach }) // end spread @@ -943,7 +960,8 @@ function onError(service, err) { tcpService.emit("serviceError", { status: 400, message: err.toString(), - service: service ? service.id : 0 + service: service ? service.id : 0, + process: err.process }); } diff --git a/webapp/locales/en_US.json b/webapp/locales/en_US.json index fa9667e63..5f92f6ba4 100644 --- a/webapp/locales/en_US.json +++ b/webapp/locales/en_US.json @@ -184,6 +184,7 @@ "Column is required": "Column is required", "Comma": "Comma", "Commands": "Commands", + "Compact": "Compact", "Condition": "Condition", "Conditioned": "Conditioned", "Confirm": "Confirm", @@ -319,6 +320,8 @@ "Dynamic Data": "Dynamic Data", "Dynamic Data Registration": "Dynamic Data Registration", "Dynamic Data Series": "Dynamic Data Series", + "Dynamic/Analysis data": "Dynamic/Analysis data", + "Dynamic/analysis data is required" : "Dynamic/analysis data is required", "E-mail": "E-mail", "East band": "East band", "Edit": "Edit", @@ -337,6 +340,7 @@ "Encoding": "Encoding", "Enter the file name!": "Enter the file name!", "Enter the table name!": "Enter the table name!", + "Erase everything": "Erase everything", "Error!": "Error!", "Error": "Error", "End": "End", @@ -610,6 +614,7 @@ "Monitored object analysis": "Monitored object analysis", "Monitored object analysis result": "Monitored object analysis result", "Monitored object attributes": "Monitored object attributes", + "Months": "Months", "More than one shapefile found!": "More than one shapefile found!", "Multiplier": "Multiplier", "Must have a valid store values to create an intersection": "Must have a valid store values to create an intersection", @@ -688,6 +693,7 @@ "PCD Data Registration": "PCD Data Registration", "POSTGIS": "POSTGIS", "Parameters": "Parameters", + "Partial erase": "Partial erase", "Password": "Password", "Password did not match for the confirmation password": "Password did not match for the confirmation password", "Password is required": "Password is required", @@ -779,6 +785,7 @@ "Legend band": "Legend band", "Legend band is required": "Legend band is required", "Legends": "Legends", + "Redefine start (Filter date will be considered)": "Redefine start (Filter date will be considered)", "Run": "Run", "SLD": "SLD", "SRID": "SRID", @@ -846,6 +853,7 @@ "Service Name": "Service Name", "Service Port": "Service Port", "Service Registration": "Service Registration", + "Service data is required": "Service data is required", "Service error": "Service error", "Service info": "Service info", "Service is required": "Service is required", @@ -906,8 +914,14 @@ "Stop": "Stop", "Stop all": "Stop all", "Stop all services": "Stop all services", + "Storage": "Storage", "Storager": "Storager", + "Storage backup parameters": "Storage backup parameters", + "Storage Registration": "Storage Registration", "Store": "Store", + "Store data": "Store data", + "Store data data is required": "Store data data is required", + "Store removed data history": "Store removed data history", "Strategy": "Strategy", "Style": "Style", "Success!": "Success!", @@ -920,6 +934,8 @@ "Table Name": "Table Name", "Table_name": "Table name", "table_name": "Table name", + "Table name": "Table name", + "Table name is required" : "Table name is required", "Temperature (Kelvin)": "Temperature (Kelvin)", "Temperature (Celsius)": "Temperature (Celsius)", "Temporal": "Temporal", @@ -946,6 +962,8 @@ "Time Unit": "Time Unit", "Time Units": "Time Units", "Time unit is required": "Time unit is required", + "Time interval": "Time interval", + "Time unit": "Time unit", "Timeout": " Request took longer than ", "Timeout: Request took longer than ": "Timeout: Request took longer than ", "Timestamp property": "Timestamp property", @@ -1031,6 +1049,7 @@ "We could not find the page you were looking for. Meanwhile, you may ": "We could not find the page you were looking for. Meanwhile, you may ", "Wednesday": "Wednesday", "Weekly": "Weekly", + "Weeks": "Weeks", "Weight average neighbor": "Weight average neighbor", "When the value is 0, it will use thread number supported by the server": "When the value is 0, it will use thread number supported by the server", "When value is 0, the default will be supported threads by server": "When value is 0, the default will be supported threads by server", @@ -1115,8 +1134,6 @@ "Invalid view name": "Invalid view name", "Invalid query builder": "Invalid query builder", "Query Builder": "Query Builder", - "Storage Registration": "Storage Registration", - "Storage": "Storage", "Monitored object analysis - VP": "Monitored object analysis - VP", "DCP - CEMADEN": "DCP - CEMADEN", "NetCDF ": { diff --git a/webapp/locales/es_ES.json b/webapp/locales/es_ES.json index 8f312d5a8..a13b4564f 100644 --- a/webapp/locales/es_ES.json +++ b/webapp/locales/es_ES.json @@ -405,7 +405,9 @@ "Seconds": "Segundos", "Hours": "Horas", "Minutes": "Minutos", + "Months": "Meses", "Weekly": "Semanal", + "Weeks": "Semana", "Grid Data Series": "Serie de Datos de Cuadrícula", "Start Time": "Hora de inicio", "Frequency": "Frecuencia", @@ -1117,5 +1119,17 @@ "DCP - PostGIS (Single Table)": "DCP - PostGIS (Tabela única)", "DCP - PostGIS (N-Table)": "DCP - PostGIS (N-Tabelas)", "Valid time interval": "Intervalo de tiempo válido", - "Interpolation Registration": "Registro de interpolador" + "Interpolation Registration": "Registro de interpolador", + "Dynamic/Analysis data": "Datos Dinámicos/Análisis", + "Dynamic/analysis data is required" : "Datos Dinámicos/Análisis es obligatorio", + "Erase everything": "Borrar todo", + "Partial erase": "Borrar parcial", + "Redefine start (Filter date will be considered)": "Restablecer el inicio (Se considerará la fecha del filtro)", + "Storage": "Almacenamiento", + "Storage backup parameters": "Parámetros de copia de seguridad de almacenamiento", + "Storage Registration": "Registro de almacenamiento", + "Store data": "Servidor de Datos de Destino", + "Store data data is required": "El servidor de datos de destino es obligatorio", + "Store removed data history": "Almacenamiento de datos eliminados", + "Table name": "Nombre de tabla" } diff --git a/webapp/locales/pt_BR.json b/webapp/locales/pt_BR.json index 606c04554..0866a0e17 100644 --- a/webapp/locales/pt_BR.json +++ b/webapp/locales/pt_BR.json @@ -185,6 +185,7 @@ "Column is required": "Coluna é requerido", "Comma": "Vírgula", "Commands": "Comandos", + "Compact": "Compactar", "Condition": "Condição", "Conditional": "Condicional", "Conditioned": "Condicionado", @@ -321,6 +322,8 @@ "Dynamic Data": "Dados Dinâmicos", "Dynamic Data Registration": "Registro de Dado Dinâmico", "Dynamic Data Series": "Séries de Dados Dinâmicos", + "Dynamic/Analysis data": "Dados Dinâmicos/Análise", + "Dynamic/analysis data is required" : "Dados Dinâmicos/Análise é obrigatório", "E-mail": "E-mail", "East band": "Banda leste", "Edit": "Editar", @@ -344,6 +347,7 @@ "Enter the table name!": "Insira o nome da tabela!", "Enviroment": "Ambiente", "Enviroment Variables": "Variáveis de Ambiente", + "Erase everything": "Apagar tudo", "Error": "Erro", "Error!": "Erro!", "Error: Error occurred during the service startup. Error: Error: exit code 127": "Erro: Ocorreu um erro durante a inicialização do serviço. O executável do serviço não foi encontrado.", @@ -623,6 +627,7 @@ "Monitored object analysis": "Análise de objeto monitorado", "Monitored object analysis result": "Resultado de análise com Objeto Monitorado", "Monitored object attributes": "Atributos do objeto monitorado", + "Months": "Meses", "More than one shapefile found!": "Mais de um shapefile encontrados!", "Multiplier": "Multiplicador", "Must have a valid store values to create an intersection": "Configuração de armazenamento deve ser válida para criar interseção", @@ -700,6 +705,7 @@ "PCD": "PCD", "PCD Data Registration": "Registro de dados de PCD", "Parameters": "Parâmetros", + "Partial erase": "Apagar parcial", "Password": "Senha", "Password did not match for the confirmation password": "A senha e a confirmação não coincidem", "Password is required": "O preenchimento da senha é obrigatório", @@ -762,6 +768,7 @@ "Python": "Python", "Radius": "Raio", "Raster": "Matriz", + "Redefine start (Filter date will be considered)": "Redefinir início (Será considerada a data do filtro)", "Register a new membership": "Registrar um novo membro", "Remember Me": "Lembrar de mim", "Remember me": "Lembrar de mim", @@ -844,6 +851,7 @@ "Service": "Serviço", "Service Name": "Nome do Serviço", "Service Data": "Serviço de Dados", + "Service data is required": "Serviço de dados é obrigatório", "Service Description": "Descrição do Serviço", "Service Executable Path": "Caminho do Executável do Serviço", "Service Name": "Nome do Serviço", @@ -909,8 +917,14 @@ "Stop": "Parar", "Stop all": "Parar todos", "Stop all services": "Parar todos serviços", + "Storage": "Armazenamento", "Storager": "Armazenamento", + "Storage backup parameters": "Parametros do backup do armazenamento", + "Storage Registration": "Registro do Armazenamento", "Store": "Armazenar", + "Store data": "Servidor de Dados Destino", + "Store data data is required": "Servidor de Dados Destino é obrigatório", + "Store removed data history": "Armazenar dados removidos", "Strategy": "Estratégia", "Style": "Estilo", "Success!": "Sucesso!", @@ -923,6 +937,8 @@ "Table Name": "Nome da tabela", "Table_name": "Nome da tabela", "table_name": "Nome da tabela", + "Table name": "Nome da tabela", + "Table name is required" : "Nome da tabela é obrigatório", "Temperature (Kelvin)": "Temperatura (Kelvin)", "Temperature (Celsius)": "Temperatura (Celsius)", "Temporal": "Temporal", @@ -949,6 +965,8 @@ "Time Unit": "Unidade de tempo", "Time Units": "Unidade de Tempo", "Time unit is required": "Unidade de tempo é requerido", + "Time interval": "Intervalo de tempo", + "Time unit": "Unidade de tempo", "Timeout": " A requisição demorou mais que ", "Timeout: Request took longer than ": "Tempo Limite do Servidor de Dados: A requisição demorou mais que ", "Timestamp property": "Propriedade de tempo", @@ -1035,6 +1053,7 @@ "Wednesday": "Quarta-feira", "Weight average neighbor": "Vizinho médio ponderado", "Weekly": "Semanalmente", + "Weeks": "Semanas", "When the value is 0, it will use thread number supported by the server": "Quando o valor for 0, será utilizado o número de threads suportado pelo servidor", "When value is 0, the default will be supported threads by server": "Quando o valor é 0, o padrão de threads será definido pelo servidor", "Wild fire event (ESRI Shapefile)": "Evento de queimada (ESRI Shapefile)", diff --git a/webapp/models/Schedule.js b/webapp/models/Schedule.js index 7351ddda2..15983cb19 100644 --- a/webapp/models/Schedule.js +++ b/webapp/models/Schedule.js @@ -65,6 +65,14 @@ module.exports = function(sequelize, DataTypes) { allowNull: false } }); + + Schedule.hasOne(models.Storages, { + onDelete: "CASCADE", + foreignKey: { + name: "schedule_id", + allowNull: true + } + }); } } } diff --git a/webapp/models/ServiceInstance.js b/webapp/models/ServiceInstance.js index 2c3d7f93a..e52cb4fd3 100644 --- a/webapp/models/ServiceInstance.js +++ b/webapp/models/ServiceInstance.js @@ -90,6 +90,12 @@ module.exports = function(sequelize, DataTypes) { allowNull: false } }); + + ServiceInstance.hasMany(models.Storages, { + foreignKey: { + allowNull: false + } + }); } } } diff --git a/webapp/models/Storage.js b/webapp/models/Storage.js new file mode 100644 index 000000000..eedce71ab --- /dev/null +++ b/webapp/models/Storage.js @@ -0,0 +1,88 @@ +module.exports = function(sequelize, DataTypes) { + var Storages = sequelize.define("Storages", + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING + }, + description: DataTypes.TEXT, + active: DataTypes.BOOLEAN, + erase_all: DataTypes.BOOLEAN, + keep_data: DataTypes.INTEGER, + keep_data_unit: DataTypes.TEXT, + filter: DataTypes.BOOLEAN, + schedule_type: DataTypes.INTEGER, + backup: DataTypes.BOOLEAN, + uri: DataTypes.TEXT, + zip: DataTypes.BOOLEAN + }, + { + underscored: true, + underscoredAll: true, + timestamps: false, + + classMethods: { + associate: function(models) { + Storages.belongsTo(models.Project, { + onDelete: "CASCADE", + foreignKey: { + name: "project_id", + allowNull: false + } + }); + + Storages.belongsTo(models.DataProvider, { + onDelete: "CASCADE", + foreignKey: { + name: "data_provider_id", + allowNull: true + } + }); + + Storages.belongsTo(models.DataSeries, { + onDelete: "CASCADE", + foreignKey: { + name: "data_series_id", + allowNull: false + } + }); + + Storages.belongsTo(models.Schedule, { + onDelete: "CASCADE", + foreignKey: { + name: "schedule_id", + allowNull: true, + constraints: true + } + }); + + Storages.belongsTo(models.AutomaticSchedule, { + onDelete: "CASCADE", + foreignKey: { + name: "automatic_schedule_id", + allowNull: true, + constraints: true + } + }); + + Storages.belongsTo(models.ServiceInstance, { + onDelete: "CASCADE", + foreignKey: { + name: "service_instance_id", + allowNull: true, + constraints: true + } + }); + + } + } + } + ); + + return Storages; +}; diff --git a/webapp/models/Storages_historic.js b/webapp/models/Storages_historic.js new file mode 100644 index 000000000..2a7f49598 --- /dev/null +++ b/webapp/models/Storages_historic.js @@ -0,0 +1,46 @@ +module.exports = function(sequelize, DataTypes) { +"use strict"; + + var storage_historic = sequelize.define("storage_historic", + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + process_id: { + type: DataTypes.INTEGER, + allowNull: false + }, + status: { + type: DataTypes.INTEGER, + allowNull: false + }, + start_timestamp: DataTypes.DATE, + data_timestamp: DataTypes.DATE, + last_process_timestamp: DataTypes.DATE, + data: DataTypes.TEXT, + origin: DataTypes.TEXT, + type: DataTypes.INTEGER, + description: DataTypes.TEXT + }, + { + underscored: true, + underscoredAll: true, + timestamps: false, + classMethods: { + associate: function(models) { + storage_historic.belongsTo(models.Storages, { + onDelete: "CASCADE", + foreignKey: { + name: "storage_id", + allowNull: false + } + }); + } + } + }); + +return storage_historic; +}; diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 4427db519..4fd4c4be7 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -1,6 +1,6 @@ { "name": "terrama2-webapp", - "version": "4.0.8-release", + "version": "4.1.0-release", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -815,6 +815,22 @@ "semver": "^5.3.0" } }, + "@babel/runtime-corejs2": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.4.3.tgz", + "integrity": "sha512-anTLTF7IK8Hd5f73zpPzt875I27UaaTWARJlfMGgnmQhvEe1uNHQRKBUbXL0Gc0VEYiVzsHsTPso5XdK8NGvFg==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } + }, "@babel/template": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", @@ -885,6 +901,50 @@ } } }, + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", @@ -909,6 +969,10 @@ "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.2.8.tgz", "integrity": "sha1-Bi3EV3KwDs5qVHomajCB/ldZEQM=" }, + "active-ftp": { + "version": "git+https://github.com/TerraMA2/active-ftp.git#d435e1223dd25996c8f66919fa43956b0e6c5c4e", + "from": "git+https://github.com/TerraMA2/active-ftp.git" + }, "addressparser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", @@ -1149,6 +1213,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", @@ -1179,6 +1249,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -1265,13 +1341,439 @@ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" }, + "basic-ftp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-3.5.0.tgz", + "integrity": "sha512-6c7bH9+mdkQy8fWNrF5GRsWfKU3QjEFzSCbqnrKtwyMIIh56wbcWodE9c+Z2Va4iRVlqFpR0e7bkaHghk1Sd1w==" + }, "bcrypt": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-0.8.7.tgz", - "integrity": "sha1-vDh1qa/Qp7LNIxpqfyGKXOFWsJM=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.4.tgz", + "integrity": "sha512-XqmCym97kT6l+jFEKeFvGuNE9aVEFDGsLMv+tIBTXkJI1sHS0g8s7VQEPJagSMPwWiB5Vpr2kVzVKc/YfwWthA==", "requires": { - "bindings": "1.2.1", - "nan": "2.3.5" + "nan": "2.12.1", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.4", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.1.1", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true + }, + "npm-packlist": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.5", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.6.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } } }, "bcrypt-pbkdf": { @@ -1304,11 +1806,6 @@ "chainsaw": "~0.1.0" } }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" - }, "blob": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", @@ -1387,6 +1884,12 @@ "repeat-element": "^1.1.2" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-zlib": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", @@ -1493,6 +1996,20 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -1539,6 +2056,12 @@ "color-name": "^1.0.0" } }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", @@ -1914,6 +2437,11 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1924,6 +2452,43 @@ "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" }, + "cron": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.7.0.tgz", + "integrity": "sha512-I7S7ES2KZtKPfBTGJ5Brc6X23apE71fgYU/PC5ayh8R6VhECpqvTLe/LTkwAEN3ERFzNKXlWzh/PkwsGg3vkDQ==", + "requires": { + "moment-timezone": "^0.5.x" + }, + "dependencies": { + "moment-timezone": { + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", + "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", + "requires": { + "moment": ">= 2.9.0" + } + } + } + }, + "cron-parser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.11.0.tgz", + "integrity": "sha512-L5LAGlvq2xmCLErhjQRX8IL5v72y8jhGOaxrarYOhse0kJjJGb/vY/0sV/c7F/SylJGkUIY2iZPPJXZD3glZqA==", + "requires": { + "is-nan": "^1.2.1", + "moment-timezone": "^0.5.23" + }, + "dependencies": { + "moment-timezone": { + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", + "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", + "requires": { + "moment": ">= 2.9.0" + } + } + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -1945,312 +2510,40 @@ } } }, - "crossfilter2": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/crossfilter2/-/crossfilter2-1.4.6.tgz", - "integrity": "sha512-Fdmb6NqdUqreOuQ9qiNvTLOxMFBQRPDeRBBnqM8Zlebdf2i7Bn5hRhE8RlO9YzbGRyYxvYzdDXt6C1muyanolg==", - "requires": { - "lodash.result": "^4.4.0" - } - }, "csextends": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/csextends/-/csextends-1.1.1.tgz", "integrity": "sha1-zFPBNJ+vfwrmzfb2xKTZFW08TsE=", "requires": { "coffee-script": "^1.12.5" - }, - "dependencies": { - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha1-wF2uDLeVkdBbMHCoQzqYyaiczFM=" - } - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "^1.0.1" - } - }, - "cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "requires": { - "es5-ext": "^0.10.9" - } - }, - "d3": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.9.2.tgz", - "integrity": "sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ==", - "requires": { - "d3-array": "1", - "d3-axis": "1", - "d3-brush": "1", - "d3-chord": "1", - "d3-collection": "1", - "d3-color": "1", - "d3-contour": "1", - "d3-dispatch": "1", - "d3-drag": "1", - "d3-dsv": "1", - "d3-ease": "1", - "d3-fetch": "1", - "d3-force": "1", - "d3-format": "1", - "d3-geo": "1", - "d3-hierarchy": "1", - "d3-interpolate": "1", - "d3-path": "1", - "d3-polygon": "1", - "d3-quadtree": "1", - "d3-random": "1", - "d3-scale": "2", - "d3-scale-chromatic": "1", - "d3-selection": "1", - "d3-shape": "1", - "d3-time": "1", - "d3-time-format": "2", - "d3-timer": "1", - "d3-transition": "1", - "d3-voronoi": "1", - "d3-zoom": "1" - } - }, - "d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" - }, - "d3-axis": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", - "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" - }, - "d3-brush": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz", - "integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - }, - "d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", - "requires": { - "d3-array": "1", - "d3-path": "1" - } - }, - "d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" - }, - "d3-color": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", - "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==" - }, - "d3-contour": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", - "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", - "requires": { - "d3-array": "^1.1.1" - } - }, - "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" - }, - "d3-drag": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz", - "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==", - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - }, - "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" - }, - "d3-fetch": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", - "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", - "requires": { - "d3-dsv": "1" - } - }, - "d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, - "d3-format": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", - "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" - }, - "d3-geo": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz", - "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==", - "requires": { - "d3-array": "1" - } - }, - "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" - }, - "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", - "requires": { - "d3-color": "1" - } - }, - "d3-path": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz", - "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==" - }, - "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" - }, - "d3-quadtree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", - "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" - }, - "d3-random": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" - }, - "d3-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", - "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "d3-scale-chromatic": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz", - "integrity": "sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==", - "requires": { - "d3-color": "1", - "d3-interpolate": "1" - } - }, - "d3-selection": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", - "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" - }, - "d3-shape": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", - "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", - "requires": { - "d3-path": "1" - } - }, - "d3-time": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", - "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==" - }, - "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", - "requires": { - "d3-time": "1" + }, + "dependencies": { + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha1-wF2uDLeVkdBbMHCoQzqYyaiczFM=" + } } }, - "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" - }, - "d3-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", - "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" + "array-find-index": "^1.0.1" } }, - "d3-voronoi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", - "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" }, - "d3-zoom": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.3.tgz", - "integrity": "sha512-xEBSwFx5Z9T3/VrwDkMt+mr0HCzv7XjpGURJ8lWmIC8wxe32L39eWHIasEe/e7Ox8MPU4p1hvH8PKN2olLzIBg==", + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" + "es5-ext": "^0.10.9" } }, "dashdash": { @@ -2278,15 +2571,6 @@ "meow": "^3.3.0" } }, - "dc": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/dc/-/dc-3.0.12.tgz", - "integrity": "sha512-TDn+axgyYW80rba+ILLjY302eVdY45+UOHo5rDF4Pjr2dsKRrvt0MQcwgjbf0jnAHMxdwunJYaPijL0yj+G9jw==", - "requires": { - "crossfilter2": "^1.4.5", - "d3": "^5.8.0" - } - }, "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", @@ -2305,6 +2589,15 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -2320,6 +2613,14 @@ } } }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -2356,6 +2657,12 @@ "fs-exists-sync": "^0.1.0" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "dottie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz", @@ -3105,15 +3412,6 @@ } } }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - } - }, "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -3132,6 +3430,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -3375,6 +3679,12 @@ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "grunt": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.3.tgz", @@ -3806,6 +4116,12 @@ } } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "homedir-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", @@ -3899,6 +4215,11 @@ "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz", "integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4=" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -4098,6 +4419,14 @@ "is-extglob": "^1.0.0" } }, + "is-nan": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", + "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", + "requires": { + "define-properties": "^1.1.1" + } + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -4320,6 +4649,11 @@ "graceful-fs": "^4.1.6" } }, + "jsonic": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/jsonic/-/jsonic-0.3.1.tgz", + "integrity": "sha512-5Md4EK3vPAMvP2sXY6M3/vQEPeX3LxEQBJuF979uypddXjsUlEoAI9/Nojh8tbw+YU5FjMoqSElO6oyjrAuprw==" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -4331,6 +4665,62 @@ "verror": "1.10.0" } }, + "jszip": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", + "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -4382,6 +4772,14 @@ "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=" }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "liftoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", @@ -4780,11 +5178,6 @@ "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, - "lodash.result": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.result/-/lodash.result-4.5.2.tgz", - "integrity": "sha1-y0Wyf7kU6qjY7m8M57KHC4fLcKo=" - }, "lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", @@ -4810,6 +5203,17 @@ "lodash.escape": "^3.0.0" } }, + "lolex": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", + "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", + "dev": true + }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4912,6 +5316,13 @@ "integrity": "sha1-2+2lsGsySZc8bfYXD94jhvCv2JM=", "requires": { "xregexp": "^2.0.0" + }, + "dependencies": { + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" + } } }, "maxmin": { @@ -5132,6 +5543,71 @@ } } }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "moment": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", @@ -5216,9 +5692,9 @@ "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=" }, "nan": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.5.tgz", - "integrity": "sha1-gioNwmYpDOTNOhIoLKPn42Rmigg=" + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" }, "nanomatch": { "version": "1.2.7", @@ -5275,6 +5751,36 @@ "resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz", "integrity": "sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ=" }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-releases": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.10.tgz", @@ -5284,6 +5790,16 @@ "semver": "^5.3.0" } }, + "node-schedule": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.2.tgz", + "integrity": "sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==", + "requires": { + "cron-parser": "^2.7.3", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.0.0" + } + }, "nodemailer": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", @@ -5453,6 +5969,11 @@ } } }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -5646,9 +6167,9 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "packet-reader": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz", - "integrity": "sha1-gZ300BC4LV6lZx+KGjrPA5vNdwA=" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", + "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" }, "pako": { "version": "0.2.9", @@ -5796,6 +6317,12 @@ "pinkie-promise": "^2.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -5812,24 +6339,43 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/pg/-/pg-4.5.7.tgz", - "integrity": "sha1-Ra4WsjcGpjRaAyed7MaveVwW0ps=", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-6.4.2.tgz", + "integrity": "sha1-w2QBEGDqx6UHoq4GPrhX7OkQ4n8=", "requires": { "buffer-writer": "1.0.1", - "generic-pool": "2.4.2", "js-string-escape": "1.0.1", - "packet-reader": "0.2.0", + "packet-reader": "0.3.1", "pg-connection-string": "0.1.3", + "pg-pool": "1.*", "pg-types": "1.*", - "pgpass": "0.0.3", - "semver": "^4.1.0" + "pgpass": "1.*", + "semver": "4.3.2" }, "dependencies": { + "generic-pool": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz", + "integrity": "sha1-eAw29p360FpaBF3Te+etyhGk9v8=" + }, + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" + }, + "pg-pool": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz", + "integrity": "sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc=", + "requires": { + "generic-pool": "2.4.3", + "object-assign": "4.1.0" + } + }, "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" } } }, @@ -5838,6 +6384,15 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" }, + "pg-custom-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pg-custom-types/-/pg-custom-types-3.0.0.tgz", + "integrity": "sha1-9yzG+ozZf8mOEm5OxavrYrhfU5o=", + "requires": { + "pg-format": "^1.0.2", + "postgres-array": "^1.0.0" + } + }, "pg-format": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", @@ -5856,6 +6411,112 @@ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, + "pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" + }, + "pg-postgis-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pg-postgis-types/-/pg-postgis-types-3.0.0.tgz", + "integrity": "sha1-wZXVP726+mEbAF7Cm9B6zRslUmk=", + "requires": { + "pg-custom-types": "^3.0.0", + "postgres-array": "^1.0.0", + "wkx": "^0.3.0" + }, + "dependencies": { + "wkx": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.3.0.tgz", + "integrity": "sha1-TySUjYeaymXlAWdEaYVeBMKteN0=" + } + } + }, + "pg-structure": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/pg-structure/-/pg-structure-4.2.2.tgz", + "integrity": "sha512-CyUkQFgu753bC1+5E6U1XjvF4pYGxLsNUjt5MDptgYRq1+DhIN8h0xIwxDpWBrk3XOwVQk6YtVq8P4WvQl1jcg==", + "requires": { + "fs-extra": "^7.0.1", + "inflection": "^1.8.0", + "jsonic": "^0.3.1", + "jszip": "^3.1.5", + "lodash": "^4.17.11", + "pg": "^7.8.0", + "semver": "^5.6.0" + }, + "dependencies": { + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "pg": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.10.0.tgz", + "integrity": "sha512-aE6FZomsyn3OeGv1oM50v7Xu5zR75c15LXdOCwA9GGrfjXsQjzwYpbcTS6OwEMhYfZQS6m/FVU/ilPLiPzJDCw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "0.1.3", + "pg-pool": "^2.0.4", + "pg-types": "~2.0.0", + "pgpass": "1.x", + "semver": "4.3.2" + }, + "dependencies": { + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + } + } + }, + "pg-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", + "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, "pg-types": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", @@ -5869,11 +6530,11 @@ } }, "pgpass": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-0.0.3.tgz", - "integrity": "sha1-EuZ+NDsxicLzEgbrycwL7//PkUA=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", "requires": { - "split": "~0.3" + "split": "^1.0.0" } }, "pify": { @@ -5900,9 +6561,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postgres-array": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", - "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", + "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" }, "postgres-bytea": { "version": "1.0.0", @@ -5910,14 +6571,14 @@ "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" }, "postgres-date": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", - "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", + "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" }, "postgres-interval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", - "integrity": "sha1-rNsPiXtLHG5JbZ1OCoU+HEKPBvA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { "xtend": "^4.0.0" } @@ -6365,11 +7026,6 @@ "glob": "^7.1.3" } }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -6604,6 +7260,11 @@ "to-object-path": "^0.3.0" } }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -6653,6 +7314,32 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "sinon": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", + "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^4.0.1", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "slice-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz", @@ -6912,6 +7599,11 @@ "smart-buffer": "^1.0.4" } }, + "sorted-array-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz", + "integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg==" + }, "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -6961,9 +7653,9 @@ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" }, "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { "through": "2" } @@ -7497,6 +8189,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.15", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", @@ -8153,9 +8851,12 @@ "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=" }, "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz", + "integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==", + "requires": { + "@babel/runtime-corejs2": "^7.2.0" + } }, "xtend": { "version": "4.0.1", diff --git a/webapp/package.json b/webapp/package.json index 9321badef..fc558b020 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,7 +14,7 @@ "dependencies": { "ace-builds": "=1.2.8", "active-ftp": "git+https://github.com/TerraMA2/active-ftp.git", - "adm-zip": "^0.4.11", + "adm-zip": "^0.4.13", "admin-lte": "=2.3.0", "almond": "=0.3.3", "angular": "=1.6.5", @@ -41,8 +41,8 @@ "connect-flash": "^0.1.1", "connect-multiparty": "^2.0.0", "cookie-parser": "~1.3.5", + "cron": "^1.7.0", "datatables": "=1.10.13", - "dc": "^3.0.12", "debug": "~2.2.0", "eonasdan-bootstrap-datetimepicker": "=4.17.47", "express": "~4.13.1", @@ -52,7 +52,7 @@ "font-awesome": "=4.6.3", "fs": "0.0.1-security", "glob": "^7.0.3", - "grunt": "^1.0.2", + "grunt": "^1.0.3", "grunt-contrib-clean": "^1.0.0", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-cssmin": "^1.0.2", @@ -69,12 +69,14 @@ "moment": "=2.18.1", "morgan": "~1.6.1", "ng-file-upload": "=12.2.13", + "node-schedule": "^1.3.2", "nodemailer": "=2.7.2", "passport": "^0.3.2", "passport-local": "^1.0.0", - "pg": "^4.5.1", + "pg": "=6.0.0", "pg-format": "^1.0.4", "pg-hstore": "^2.3.2", + "pg-pool": "^2.0.6", "request": "^2.87.0", "requirejs": "^2.3.2", "rimraf": "^2.6.1", @@ -87,7 +89,8 @@ "swig": "1.4.2", "ui-select": "=0.19.8", "unzip2": "^0.2.5", - "winston": "^2.3.0" + "winston": "^2.4.3", + "xregexp": "^4.2.4" }, "devDependencies": { "@babel/core": "^7.0.0-beta.49", @@ -97,5 +100,10 @@ "mocha": "^5.2.0", "sinon": "^7.2.3", "uglify-es": "^3.3.9" - } + }, + "description": "**NOTE:** **NOTE:*** **Check the website for the most recent stable version: http://www.terrama2.dpi.inpe.br/** * **If you have any question, please, send us an e-mail at: terrama2-team@dpi.inpe.br.**", + "main": "Gruntfile.js", + "keywords": [], + "author": "", + "license": "ISC" } diff --git a/webapp/public/javascripts/angular/alert-box/directives/index.js b/webapp/public/javascripts/angular/alert-box/directives/index.js index 313251cbe..501148f5b 100644 --- a/webapp/public/javascripts/angular/alert-box/directives/index.js +++ b/webapp/public/javascripts/angular/alert-box/directives/index.js @@ -11,7 +11,7 @@ define([ .run(["$templateCache", function($templateCache) { // $templateCache.put('message-box.html', - '
' + + '
' + '' + '

{{ title }}

' + '
' + diff --git a/webapp/public/javascripts/angular/alert-box/templates/alert-box.html b/webapp/public/javascripts/angular/alert-box/templates/alert-box.html deleted file mode 100644 index 7c604f810..000000000 --- a/webapp/public/javascripts/angular/alert-box/templates/alert-box.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -

{{ title }}

- {{ message }} -
\ No newline at end of file diff --git a/webapp/public/javascripts/angular/application.js b/webapp/public/javascripts/angular/application.js index 2557b6c99..1a0e3308c 100644 --- a/webapp/public/javascripts/angular/application.js +++ b/webapp/public/javascripts/angular/application.js @@ -13,9 +13,11 @@ define([ "TerraMA2WebApp/users/app", "TerraMA2WebApp/alerts/app", "TerraMA2WebApp/legends/app", - "TerraMA2WebApp/interpolator/app" -], function(moduleLoader, commonModule, countriesModule, alertBoxModule, serviceModule, projectModule, - statusModule, dataProviderModule, dataSeriesModule, viewsModule, analysisModule, userModule, alertModule, legendModule, interpolatorModule) { + "TerraMA2WebApp/interpolator/app", + "./storage/app" + +], function(moduleLoader, commonModule, countriesModule, alertBoxModule, serviceModule, projectModule, + statusModule, dataProviderModule, dataSeriesModule, viewsModule, analysisModule, userModule, alertModule, legendModule, interpolatorModule, storageModule) { var moduleName = "terrama2"; var deps = [commonModule, countriesModule]; @@ -32,6 +34,7 @@ define([ moduleLoader(alertModule, deps); moduleLoader(legendModule, deps); moduleLoader(interpolatorModule, deps); + moduleLoader(storageModule, deps); var terrama2Module = angular.module(moduleName, deps); @@ -40,4 +43,4 @@ define([ }; return terrama2Module; -}); \ No newline at end of file +}); diff --git a/webapp/public/javascripts/angular/common/directives/datetime-formatter.js b/webapp/public/javascripts/angular/common/directives/datetime-formatter.js index cb527b590..6834a9907 100644 --- a/webapp/public/javascripts/angular/common/directives/datetime-formatter.js +++ b/webapp/public/javascripts/angular/common/directives/datetime-formatter.js @@ -37,7 +37,7 @@ define(function() { * @return {string} */ function formatter(value) { - var m = moment(value || null); + var m = moment(value, fmt || 'LLLL'); var valid = m.isValid(); if (valid) { return m.format(fmt || "LLLL"); diff --git a/webapp/public/javascripts/angular/common/directives/datetime.js b/webapp/public/javascripts/angular/common/directives/datetime.js index 6873b9abf..ee4118d38 100644 --- a/webapp/public/javascripts/angular/common/directives/datetime.js +++ b/webapp/public/javascripts/angular/common/directives/datetime.js @@ -44,7 +44,10 @@ define( $timeout(function () { if (!!ngModelCtrl.$viewValue) { if (!(ngModelCtrl.$viewValue instanceof moment)) { - ngModelCtrl.$setViewValue(moment(scope.date)); + const { options } = scope; + let fmt = options ? (options.format || 'LLL') : 'LLL'; + + ngModelCtrl.$setViewValue(moment(scope.dateTime, fmt)); } element.data('DateTimePicker').date(ngModelCtrl.$viewValue); } diff --git a/webapp/public/javascripts/angular/common/directives/fluid.js b/webapp/public/javascripts/angular/common/directives/fluid.js deleted file mode 100644 index 0f058d2c4..000000000 --- a/webapp/public/javascripts/angular/common/directives/fluid.js +++ /dev/null @@ -1,42 +0,0 @@ -define(function() { - function terrama2Fluid($window) { - return { - restrict: "A", - link: function(scope, element, attrs) { - var windowElement = angular.element($window); - var bodyElement = angular.element("body"); - /** - * Helper to handle window size and add/remove class depending resolution - * - * @return {void} - */ - function resizeComponent() { - var screenY = $window.innerHeight; - var scrollHeight = bodyElement[0].scrollHeight; - // var elementHeight = element[0].clientHeight; - var currentOffSet = $window.pageYOffset; - var scrollScreenFactor = currentOffSet / scrollHeight; // percentage - var factor = 200 / scrollHeight; - - if (scrollScreenFactor + factor > 0.3) { - element.addClass("terrama2-fluid"); - } else { - element.removeClass("terrama2-fluid"); - } - } - - // Auto call to resize at first time - resizeComponent(); - - // Performs resize component on page scroll - windowElement.bind('scroll', function() { - return resizeComponent(); - }); - } - }; - } - - terrama2Fluid.$inject = ["$window"]; - - return terrama2Fluid; -}); \ No newline at end of file diff --git a/webapp/public/javascripts/angular/common/directives/index.js b/webapp/public/javascripts/angular/common/directives/index.js index 5e08b0d3f..737b76e06 100644 --- a/webapp/public/javascripts/angular/common/directives/index.js +++ b/webapp/public/javascripts/angular/common/directives/index.js @@ -1,21 +1,18 @@ define([ "TerraMA2WebApp/common/directives/box", - "TerraMA2WebApp/common/directives/button", "TerraMA2WebApp/common/directives/content", "TerraMA2WebApp/common/directives/datetime", "TerraMA2WebApp/common/directives/datetime-formatter", - "TerraMA2WebApp/common/directives/fluid", "TerraMA2WebApp/common/directives/show-errors", "TerraMA2WebApp/common/directives/compare-to", "TerraMA2WebApp/common/directives/text-select" -], function(terrama2Box, terrama2Button, terrama2Content, terrama2Datetime, terrama2DateTimeFormatter, terrama2Fluid, terrama2ShowErrors, terrama2CompareTo, terrama2TextSelect) { +], function(terrama2Box, terrama2Content, terrama2Datetime, terrama2DateTimeFormatter, terrama2ShowErrors, terrama2CompareTo, terrama2TextSelect) { var moduleName = "terrama2.common.directives"; angular.module(moduleName, []) .directive("terrama2Box", terrama2Box) .directive("terrama2Content", terrama2Content) .directive("terrama2Datetime", terrama2Datetime) .directive("formatDatetime", terrama2DateTimeFormatter) - .directive("terrama2Fluid", terrama2Fluid) .directive("terrama2ShowErrors", terrama2ShowErrors) .directive("terrama2CompareTo", terrama2CompareTo) .directive("terrama2TextSelect", terrama2TextSelect) diff --git a/webapp/public/javascripts/angular/data-provider/controllers/index.js b/webapp/public/javascripts/angular/data-provider/controllers/index.js index efc407de4..aeef36ec7 100644 --- a/webapp/public/javascripts/angular/data-provider/controllers/index.js +++ b/webapp/public/javascripts/angular/data-provider/controllers/index.js @@ -3,7 +3,7 @@ define([ "TerraMA2WebApp/alert-box/app", "TerraMA2WebApp/data-provider/controllers/list", "TerraMA2WebApp/data-provider/controllers/registration" -], function(commonServiceModule, alertBoxModule, ListController, RegisterController) { +], (commonServiceModule, alertBoxModule, ListController, RegisterController) => { var moduleName = "terrama2.providers.controllers"; var deps = [commonServiceModule, alertBoxModule]; diff --git a/webapp/public/javascripts/angular/data-provider/controllers/list.js b/webapp/public/javascripts/angular/data-provider/controllers/list.js index 9e3f2ed56..086c29131 100644 --- a/webapp/public/javascripts/angular/data-provider/controllers/list.js +++ b/webapp/public/javascripts/angular/data-provider/controllers/list.js @@ -1,4 +1,4 @@ -define(function() { +define([], () => { function ListController($scope, $http, DataProviderService, i18n, $window, MessageBoxService, $q, $timeout) { var config = $window.configuration; $scope.loading = true; @@ -8,7 +8,7 @@ define(function() { i18n.ensureLocaleIsLoaded(), DataProviderService.init() ]) - + .then(function() { var title = "Data Server"; $scope.i18n = i18n; @@ -80,7 +80,7 @@ define(function() { $scope.iconFn = config.iconFn || null; $scope.iconProperties = config.iconProperties || {}; - + $scope.method = "{[ method ]}"; if(config.message) { var messageArray = config.message.split(" "); @@ -93,11 +93,11 @@ define(function() { }, 1000); } }) - + .finally(function() { $scope.loading = false; }); // end then - } + } ListController.$inject = ["$scope", "$http", "DataProviderService", "i18n", "$window", "MessageBoxService", "$q", "$timeout"]; return ListController; diff --git a/webapp/public/javascripts/angular/data-provider/controllers/registration.js b/webapp/public/javascripts/angular/data-provider/controllers/registration.js index 3b7f7f436..b022cdfd1 100644 --- a/webapp/public/javascripts/angular/data-provider/controllers/registration.js +++ b/webapp/public/javascripts/angular/data-provider/controllers/registration.js @@ -1,4 +1,4 @@ -define(function() { +define([], () => { function RegisterController($scope, $http, $q, $window, $httpParamSerializer, $location, i18n, $timeout, DataProviderService, MessageBoxService, FormTranslator) { $scope.i18n = i18n; var model = {}; @@ -401,4 +401,4 @@ define(function() { RegisterController.$inject = ["$scope", "$http", "$q", "$window", "$httpParamSerializer", "$location", "i18n", "$timeout", "DataProviderService", "MessageBoxService", "FormTranslator"]; return RegisterController; -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/webapp/public/javascripts/angular/data-series/services.js b/webapp/public/javascripts/angular/data-series/services.js index feadf5c4d..929a6ba53 100644 --- a/webapp/public/javascripts/angular/data-series/services.js +++ b/webapp/public/javascripts/angular/data-series/services.js @@ -86,6 +86,17 @@ define([ DataSeriesService.prototype.list = function(restriction) { return this.$filter('filter')(this.model, restriction); }; + + /** + * Retrieves all dynamic data series + * + * @param {Object} restriction - a query restriction + */ + DataSeriesService.prototype.getDynamicDataSeries = function(restriction) { + const dynamicDataSeriesList = this.list({ data_series_semantics: { temporality: 'DYNAMIC' } }); + + return this.$filter('filter')(dynamicDataSeriesList, restriction); + } /** * It performs DataSeries update over API * diff --git a/webapp/public/javascripts/angular/projects/controllers/list.js b/webapp/public/javascripts/angular/projects/controllers/list.js index 455016d9b..23637b1ea 100644 --- a/webapp/public/javascripts/angular/projects/controllers/list.js +++ b/webapp/public/javascripts/angular/projects/controllers/list.js @@ -32,14 +32,16 @@ define(function() { "Views": [], "Legends": [], "Alerts": [], - "Interpolators": [] + "Interpolators": [].toString, + "Storages": [] }; $scope.services = { COLLECT: [], ANALYSIS: [], VIEW: [], ALERT: [], - INTERPOLATOR: [] + INTERPOLATOR: [], + STORAGE: [] }; $scope.selectedServices = {}; $scope.hasCollect = false; @@ -317,6 +319,7 @@ define(function() { delete $scope.exportData.Legends; delete $scope.exportData.Alerts; delete $scope.exportData.Interpolators; + delete $scope.exportData.Storages; } else { if($scope.projectsCheckboxes[element.id].DataProviders != undefined) { for(var j = 0, dataProvidersLength = $scope.dataProviders[element.id].length; j < dataProvidersLength; j++) { @@ -390,6 +393,13 @@ define(function() { } } + if($scope.projectsCheckboxes[element.id].Storages != undefined) { + for(var j = 0, StoragesLength = $scope.Storages[element.id].length; j < StoragesLength; j++) { + if($scope.projectsCheckboxes[element.id].Storages[$scope.Storages[element.id][j].id]) + $scope.exportData.Storages.push($scope.iStorages[element.id][j]); + } + } + if($scope.exportData.Projects.length == 0) delete $scope.exportData.Projects; if($scope.exportData.DataProviders.length == 0) delete $scope.exportData.DataProviders; if($scope.exportData.DataSeries.length == 0) delete $scope.exportData.DataSeries; @@ -399,6 +409,7 @@ define(function() { if($scope.exportData.Legends.length == 0) delete $scope.exportData.Legends; if($scope.exportData.Alerts.length == 0) delete $scope.exportData.Alerts; if($scope.exportData.Interpolators.length == 0) delete $scope.exportData.Interpolators; + if($scope.exportData.Storages.length == 0) delete $scope.exportData.Storages; } } @@ -417,7 +428,8 @@ define(function() { "Views": [], "Legends": [], "Alerts": [], - "Interpolators": [] + "Interpolators": [], + "Storage": [] }; $('#exportModal').modal('hide'); @@ -451,7 +463,8 @@ define(function() { !json.hasOwnProperty("Views") && !json.hasOwnProperty("Legends") && !json.hasOwnProperty("Alerts") && - !json.hasOwnProperty("Interpolators")) { + !json.hasOwnProperty("Interpolators")&& + !json.hasOwnProperty("Storages")) { MessageBoxService.danger(i18n.__(importTitle), new Error(i18n.__("Invalid configuration file"))); return; } @@ -467,6 +480,7 @@ define(function() { $scope.hasLegend = false; $scope.hasAlert = false; $scope.hasInterpolator = false; + $scope.hasStorage = false; if(json.Projects !== undefined && json.Projects.length > 0) $scope.hasProject = true; if(json.Collectors !== undefined && json.Collectors.length > 0) $scope.hasCollect = true; @@ -475,6 +489,7 @@ define(function() { if(json.Legends !== undefined && json.Legends.length > 0) $scope.hasLegend = true; if(json.Alerts !== undefined && json.Alerts.length > 0) $scope.hasAlert = true; if(json.Interpolators !== undefined && json.Interpolators.length > 0) $scope.hasInterpolator = true; + if(json.Storages !== undefined && json.Storages.length > 0) $scope.hasStorage = true; if(($scope.model === undefined || $scope.model.length === 0) && !$scope.hasProject) { MessageBoxService.danger(i18n.__(importTitle), new Error(i18n.__("To import this file you need to have at least one project"))); @@ -488,6 +503,8 @@ define(function() { MessageBoxService.danger(i18n.__(importTitle), new Error(i18n.__("To import this file you need to have at least one alert service"))); } else if(json.Interpolators !== undefined && json.Interpolators.length > 0 && $scope.services.INTERPOLATOR.length === 0) { MessageBoxService.danger(i18n.__(importTitle), new Error(i18n.__("To import this file you need to have at least one interpolator service"))); + } else if(json.Storages !== undefined && json.Storages.length > 0 && $scope.services.STORAGE.length === 0) { + MessageBoxService.danger(i18n.__(importTitle), new Error(i18n.__("To import this file you need to have at least one storage service"))); } else { $('#importModal').modal('show'); } @@ -518,7 +535,10 @@ define(function() { $scope.extra.importJson['servicesAlert'] = $scope.selectedServices.Alert; if($scope.hasInterpolator) - $scope.extra.importJson['servicesInterpolator'] = $scope.selectedServices.Interpolator; + $scope.extra.importJson['servicesInterpolator'] = $scope.selectedServices.Interpolator; + + if($scope.hasStorage) + $scope.extra.importJson['servicesStorage'] = $scope.selectedServices.Storage; socket.emit("import", $scope.extra.importJson); $('#importModal').modal('hide'); @@ -550,6 +570,9 @@ define(function() { case 5: $scope.services.INTERPOLATOR.push(services.data[j]); break; + case 6: + $scope.services.STORAGE.push(services.data[j]); + break; default: break; } @@ -571,6 +594,7 @@ define(function() { $scope.legends = {}; $scope.collectors = {}; $scope.interpolators = {}; + $scope.storages = {}; response.data.map(function(project, index) { // TODO: Review how to handle swipe diff --git a/webapp/public/javascripts/angular/services/service.js b/webapp/public/javascripts/angular/services/service.js index 4f21f737c..50b29b81b 100644 --- a/webapp/public/javascripts/angular/services/service.js +++ b/webapp/public/javascripts/angular/services/service.js @@ -190,6 +190,10 @@ define([], function() { break; case 5: service.type = i18n.__("Interpolation"); + break; + case 6: + service.type = i18n.__("Storage"); + break; default: break; } @@ -244,7 +248,10 @@ define([], function() { case 5: return BASE_URL + "images/services/interpolation/interpolation_service.png"; break; - default: + case 6: + return BASE_URL + "images/services/interpolation/storage_service.png"; + break; + default: break; } }; diff --git a/webapp/public/javascripts/angular/services/services.js b/webapp/public/javascripts/angular/services/services.js index 4149e8665..a7391e0a5 100644 --- a/webapp/public/javascripts/angular/services/services.js +++ b/webapp/public/javascripts/angular/services/services.js @@ -12,15 +12,16 @@ define([ ANALYSIS: 2, VIEW: 3, ALERT: 4, - INTERPOLATION: 5 + INTERPOLATION: 5, + STORAGE: 6 }); /** * It handles TerraMA² service like dao. - * + * * @param {BaseService} BaseService - TerraMA² factory to build http requests * @param {i18n} i18n - Internationalization module - * @param {ng.IPromise} $q - Angular promiser + * @param {ng.IPromise} $q - Angular promiser */ function Service(BaseService, i18n, $q, ServiceType) { /** @@ -77,7 +78,7 @@ define([ /** * It retrieves a single service from given id - * + * * @param {number} serviceId - A TerraMA² service identifier * @returns {Object} */ @@ -87,8 +88,8 @@ define([ /** * It performs a update operation in BaseService. - * - * @param {number} serviceId - TerraMA² Service identifier + * + * @param {number} serviceId - TerraMA² Service identifier * @param {Object} serviceObject - a javascript object with service values * @returns {Object} */ @@ -107,7 +108,7 @@ define([ /** * It performs a save operation in BaseService. - * + * * @param {Object} serviceObject - a javascript object with service values * @returns {Object} */ diff --git a/webapp/public/javascripts/angular/status/status.js b/webapp/public/javascripts/angular/status/status.js index 6141a4c68..7b2193ce2 100644 --- a/webapp/public/javascripts/angular/status/status.js +++ b/webapp/public/javascripts/angular/status/status.js @@ -1,17 +1,19 @@ define([ "TerraMA2WebApp/common/services/index", "TerraMA2WebApp/table/table", - "TerraMA2WebApp/alert-box/app" -],function(commonServiceModule, tableModule, alertBoxModule) { + "TerraMA2WebApp/alert-box/app", + "TerraMA2WebApp/storage/app" +],function(commonServiceModule, tableModule, alertBoxModule, storageApp) { "use strict"; var moduleName = "terrama2.status"; - angular.module(moduleName, [commonServiceModule, tableModule, alertBoxModule]) - .controller('StatusController', ['$scope', '$HttpTimeout', 'Socket', 'i18n', '$window', 'MessageBoxService', '$timeout', - function($scope, $HttpTimeout, Socket, i18n, $window, MessageBoxService, $timeout) { + angular.module(moduleName, [commonServiceModule, tableModule, alertBoxModule, storageApp]) + .controller('StatusController', ['$scope', '$HttpTimeout', 'Socket', 'i18n', '$window', 'MessageBoxService', '$timeout', 'StorageService', + async function($scope, $HttpTimeout, Socket, i18n, $window, MessageBoxService, $timeout, StorageService) { var Globals = $window.globals; var config = $window.configuration; + await StorageService.init(); $scope.logSize = 0; $scope.i18n = i18n; $scope.globals = Globals; @@ -224,6 +226,12 @@ define([ targetKey = "dataSeriesOutput"; url += `configuration/interpolator/new/${id}`; break; + case Globals.enums.ServiceType.STORAGE: + targetArray = StorageService.list(); + targetMessage = "Storage"; + targetKey = ""; + url += `administration/storages/${id}`; + break; } var logArray = response.logs; diff --git a/webapp/public/javascripts/angular/storage/app.js b/webapp/public/javascripts/angular/storage/app.js new file mode 100644 index 000000000..64eff9602 --- /dev/null +++ b/webapp/public/javascripts/angular/storage/app.js @@ -0,0 +1,14 @@ +define([ + "TerraMA2WebApp/common/app", + "TerraMA2WebApp/data-series/services", + './components/storage', + './components/storage-list', + './services/storage-service' +], (coreModule, dataSeriesServiceModule, storageComponent, storageListComponent, storageService) => { + const terrama2Module = angular.module("storage", [coreModule, dataSeriesServiceModule]) + .component("storageComponent", storageComponent) + .component('storageListComponent', storageListComponent) + .service('StorageService', storageService) + + return terrama2Module.name; +}) \ No newline at end of file diff --git a/webapp/public/javascripts/angular/storage/components/storage-list.js b/webapp/public/javascripts/angular/storage/components/storage-list.js new file mode 100644 index 000000000..2d08b3046 --- /dev/null +++ b/webapp/public/javascripts/angular/storage/components/storage-list.js @@ -0,0 +1,120 @@ +define([], () => { + class StorageListController { + constructor(i18n, Storage, MessageBox, Socket, Service, DataSeries) { + this.i18n = i18n; + this.Storage = Storage; + this.MessageBox = MessageBox; + this.Socket = Socket; + this.Service = Service; + this.DataSeries = DataSeries; + + this.init(); + + this.fields = [{key: 'name', as: i18n.__("Name")}, {key: "description", as: i18n.__("Description")}]; + this.linkToAdd = `${BASE_URL}administration/storages/new`; + this.link = (object) => { + return `${BASE_URL}administration/storages/${object.id}`; + }; + + this.extra = { + canRemove: this.hasProjectPermission || configuration.hasProjectPermission, + canRun: this.canRun, + showRunButton: true, + run: this.run.bind(this) + } + } + + /** Close alert box handler */ + close() { + this.MessageBox.reset(); + } + + /** Initializes Storage List component, loading dependencies services in memory */ + async init() { + await Promise.all([ + this.Storage.init(), + this.Service.init({ type: this.Service.types.STORAGE }), + this.DataSeries.init(), + ]) + + this.configureSocketListeners(); + } + + configureSocketListeners() { + const { Socket } = this; + + Socket.on('errorResponse', this.onErrorResponse.bind(this)); + Socket.on('runResponse', () => { + const message = this.i18n.__("The process was started successfully"); + this.MessageBox.success(this.i18n.__(`Storage`), message); + }); + } + + onStatusResponse() { + console.log(arguments); + } + + async onErrorResponse(error) { + const { message } = error; + + this.MessageBox.danger(this.i18n.__('Process'), message); + } + + canRun() { + return true; + } + + remove(object) { + return `${BASE_URL}api/storages/${object.id}`; + } + + /** + * Tries to dispatch the execution of storage + * + * @param {any} object Storage to run + */ + async run(object) { + const { DataSeries, Service, Socket } = this; + + // Make sure service exists on server side before run + const storageService = await Service.get(object.service_instance_id); + + // Make sure Data Series is inactive to run Storager + //const storageDataSeries = DataSeries.list({ id: object.data_series_id })[0]; + + //if (!storageDataSeries.active) { + return Socket.emit('run', { ids: [ object.id ], service_instance: storageService.id }); + //} + + //this.MessageBox.danger(this.i18n.__('Process'), this.i18n.__(`The storage "${object.name}" can't be executed due data series "${storageDataSeries.name}" is active. Make sure the data series is disabled in order to execute Storager.`)) + } + } + + + StorageListController.$inject = ['i18n', 'StorageService', 'MessageBoxService', 'Socket', 'Service', 'DataSeriesService']; + + const storageListComponent = { + bindings: { + hasProjectPermission: '<' + }, + controller: StorageListController, + template: ` + +
+
+
+ +
+ + + +
+
+
+ ` + } + + return storageListComponent; +}); diff --git a/webapp/public/javascripts/angular/storage/components/storage.js b/webapp/public/javascripts/angular/storage/components/storage.js new file mode 100644 index 000000000..0070b0b1a --- /dev/null +++ b/webapp/public/javascripts/angular/storage/components/storage.js @@ -0,0 +1,264 @@ +define([], function () { + /** + * Controller of StorageComponent + * + * @class StorageController + */ + class StorageController { + // Angular Dependency Injector + + /** + * @param {angular.$scope} $scope Component Scope + * @param {angular.i18n} i18n Internationalization module + * @param {DataSeriesService} DataSeries TerraMA2 Data Series DAO + */ + constructor($scope, i18n, DataSeries, Storage, Service, Provider, $q, $window, MessageBox) { + this.$scope = $scope; + this.i18n = i18n; + $scope.i18n = i18n; + $scope.css = { boxType: "box-solid" }; + this.DataSeries = DataSeries; + this.Service = Service; + this.Storage = Storage; + this.Provider = Provider; + this.isLoading = true; + this.$q = $q; + this.$window = $window; + this.MessageBox = MessageBox; + + this.scheduleOptions = { + showHistoricalOption: false, + showAutomaticOption: false + }; + + // Initialize component + this.init() + .then(() => console.log('done')) + .finally(() => { + this.isLoading = false; + }); + } + + /** Initialize Component services */ + init() { + const { DataSeries, Provider, Service, Storage } = this; + + const defer = this.$q.defer(); + + /** + * Wrap of initializer module + * + * It uses Angular promise to fix context execution + */ + const wrapExecutionWithAngularPromise = async () => { + try { + await DataSeries.init({ schema: 'all' }); + await Service.init(); + await Provider.init(); + + this.isUpdate = !!this.storageId; + + if (this.isUpdate) { + this.storage = await Storage.get(this.storageId); + + if (this.storage.schedule) { + this.$scope.$broadcast("updateSchedule", this.storage.schedule); + } else { + // default is manual + this.storage.schedule = { scheduleType: "3" } + } + + // Trigger change + this.changeDataSeries(); + this.changeDataProvider(); + + if (this.storage.uriObject) { + if (this.storage.uriObject.protocol === "FILE") { + // Parse URI + const providerURI = this.selectedProvider.uri.replace(/([^:]\/\/)\/+/g, "$1"); + const storageURI = this.storage.uri.replace(/([^:]\/\/)\/+/g, "$1"); + + let parsedPath = storageURI.replace(providerURI, ''); + + if (parsedPath[0] === '/') + parsedPath = parsedPath.substr(1); + + this.storage.path = parsedPath; + } else { + const path = this.storage.uriObject.pathname; + this.storage.path = path.slice(path.lastIndexOf("/")+1, path.length); + } + } + } + } catch (err) { + return defer.reject(err); + } + + return defer.resolve(); + }; + + wrapExecutionWithAngularPromise(); + + return defer.promise; + } + + closeBox() { + this.MessageBox.reset(); + } + + /** + * Retrieves all dynamic data series + * + * @returns {any[]} + */ + getDynamicDataSeries() { + return this.DataSeries.getDynamicDataSeries({}); + } + + /** Retrieves all Storage Services */ + getStorageServices() { + return this.Service.list({ service_type_id: this.Service.types.STORAGE }); + } + + /** Filter dynamic data series in order to retrieve POSTGIS type */ + getPostGISDataSeries() { + return this.DataSeries.getDynamicDataSeries({ data_series_semantics: { data_format_name: 'POSTGIS' } }); + } + + /** + * Get the respective data series icon in order to fill combobox + * + * @param {any} dataSeries Data series scope + */ + getDataSeriesIcon(dataSeries) { + if (!dataSeries) + return ""; + + return this.DataSeries.getIcon(dataSeries); + } + + /** Triggered when data series combobox changes. Selects the current data series into scope */ + changeDataSeries() { + this.selectedDataSeries = this.getDynamicDataSeries().find(ds => ds.id === this.storage.data_series_id); + } + + /** Triggered when data provider combobox changes. Selects the current provider into scope */ + changeDataProvider() { + this.selectedProvider = this.getDataProviders().find(provider => provider.id === this.storage.data_provider_id); + } + + + getDataProviders() { + const { selectedDataSeries } = this; + + if (!selectedDataSeries) + return; + + const providerId = selectedDataSeries.data_provider_id; + const provider = this.Provider.list({ id: providerId })[0]; + + return this.Provider.list({ data_provider_type: { id: provider.data_provider_type.id } }); + } + + /** Check if data provider is FILE */ + isDataProviderFile() { + const { selectedProvider } = this; + + if (!selectedProvider) + return false; + + const { id } = selectedProvider.data_provider_type; + + return id === 1; + } + + /** + * Validate form and format model concept + * + * @returns {boolean} + */ + validate() { + return this.storageForm.$valid && (this.parametersForm ? this.parametersForm.$valid : true); + } + + /** Trigger when cancel button clicked. It redirects to the previous page */ + onCancelClicked() { + this.$window.history.back(); + } + + async save() { + if (!this.validate()) { + this.MessageBox.danger(this.i18n.__('Error'), this.i18n.__('There are invalid fields on form')) + return; + } + + if (this.storage.backup) + this.storage.uri = this.selectedProvider.uri + "/" + this.storage.path; + + const { Storage } = this; + + if (this.storage.schedule) { + this.storage.schedule_type = this.storage.schedule.scheduleType; + switch(this.storage.schedule.scheduleHandler) { + case "seconds": + case "minutes": + case "hours": + this.storage.schedule.frequency_unit = this.storage.schedule.scheduleHandler; + this.storage.schedule.frequency_start_time = this.storage.schedule.frequency_start_time ? moment(this.storage.schedule.frequency_start_time).format("HH:mm:ssZ") : ""; + break; + case "weeks": + case "monthly": + case "yearly": + // todo: verify + const dt = this.storage.schedule.schedule_time; + this.storage.schedule.schedule_unit = this.storage.schedule.scheduleHandler; + this.storage.schedule.schedule_time = moment(dt).format("HH:mm:ss"); + break; + + default: + if (this.storage.schedule.scheduleType == "4"){ + this.storage.schedule.data_ids = [self.view.data_series_id]; + } + break; + } + } + + try { + if (this.isUpdate) { + this.storage.schedule_type = this.storage.scheduleType; + await Storage.update(this.storageId, this.storage); + } else { + await Storage.save(this.storage); + } + + this.$window.location = `${BASE_URL}administration/storages`; + } catch (err) { + console.error(err); + + this.MessageBox.danger(this.i18n.__('Error'), err.message); + } + } + } + // Angular Inject dependencies + StorageController.$inject = [ + '$scope', + 'i18n', + 'DataSeriesService', + 'StorageService', + 'Service', + 'DataProviderService', + '$q', + '$window', + 'MessageBoxService' + ]; + + const storageComponent = { + bindings: { + storageId: '=' + }, + controller: StorageController, + templateUrl: BASE_URL + "dist/templates/storage/templates/storage-component.html" + }; + + return storageComponent; +}); diff --git a/webapp/public/javascripts/angular/storage/services/storage-service.js b/webapp/public/javascripts/angular/storage/services/storage-service.js new file mode 100644 index 000000000..53ca8743e --- /dev/null +++ b/webapp/public/javascripts/angular/storage/services/storage-service.js @@ -0,0 +1,64 @@ +define([], () => { + class StorageService { + constructor(BaseService) { + this.BaseService = BaseService; + } + + async init(restriction = {}) { + const { BaseService } = this; + + try { + const response = await BaseService.$request(`${BASE_URL}api/storages`, 'GET', { params: restriction }); + + this.model = response.data; + } catch (err) { + throw err; + } + + } + + list(restriction) { + return this.BaseService.$filter('filter')(this.model, restriction); + } + + async _request(uri, method, options) { + const { BaseService } = this; + + try { + const response = await BaseService.$request(`${uri}`, method, options); + + return response.data; + } catch (err) { + throw new Error(err.message); + } + } + + find(restriction) { + const foundList = this.BaseService.$filter('filter')(this.model, restriction); + + return foundList[0]; + } + + async get(storageId) { + const response = await this._request(`${BASE_URL}api/storages/${storageId}`, 'GET'); + + return response; + } + + async save(storageObj) { + const response = await this._request(`${BASE_URL}api/storages`, 'POST', { data: storageObj }); + + return response; + } + + async update(storageId, storageObj) { + const response = await this._request(`${BASE_URL}api/storages/${storageId}`, 'PUT', { data: storageObj }); + + return response; + } + } + + StorageService.$inject = ['BaseService']; + + return StorageService; +}); \ No newline at end of file diff --git a/webapp/public/javascripts/angular/storage/templates/storage-component.html b/webapp/public/javascripts/angular/storage/templates/storage-component.html new file mode 100644 index 000000000..c93588f6b --- /dev/null +++ b/webapp/public/javascripts/angular/storage/templates/storage-component.html @@ -0,0 +1,258 @@ + +
+ +
+ + + +
+
+
+
+
+ + + + + + {{ i18n.__('Name is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.name.message }} + +
+
+ +
+
+ + + + {{ i18n.__('Service data is required') }} +
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + + + {{ i18n.__('Description is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.description.message }} + +
+
+
+
+
+
+ + + + + + + + + + + + + + {{ i18n.__('Dynamic/analysis data is required') }} +
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+ + + + + + {{ i18n.__('Time unit is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.name.message }} + +
+
+ +
+
+ + + + + + {{ i18n.__('Time interval is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.name.message }} + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
+ + + + {{ i18n.__('Store data data is required') }} +
+ +
+
+
+
+
+
+ + + + + + {{ i18n.__('Table name is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.name.message }} + +
+
+
+
+
+
+ + + + + + {{ i18n.__('Path is required') }} + + {{ i18n.__("Storage") + " " + serverErrors.name.message }} + +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/webapp/public/stylesheets/terrama2.css b/webapp/public/stylesheets/terrama2.css index 064a5fe4a..bda712193 100644 --- a/webapp/public/stylesheets/terrama2.css +++ b/webapp/public/stylesheets/terrama2.css @@ -243,12 +243,12 @@ body, html { /*}*/ -.terrama2-fluid { - position: fixed !important; - z-index: 1050 !important; - width: 100% !important; - top: 0 !important; - left: 0 !important; +.sticky { + position: fixed; + width: 100%; + top: 0; + left: 0; + z-index: 1050; } .terrama2-splash { diff --git a/webapp/routes/administration/Storage.js b/webapp/routes/administration/Storage.js new file mode 100644 index 000000000..764e2fd3d --- /dev/null +++ b/webapp/routes/administration/Storage.js @@ -0,0 +1,10 @@ + +var passport = require("../../config/Passport"); + +module.exports = function(app) { + var controllerStorage = app.controllers.administration.Storage; + + app.get(app.locals.BASE_URL + "administration/storages/", passport.isAuthenticated, controllerStorage.get); + app.get(app.locals.BASE_URL + "administration/storages/new", passport.isAuthenticated, controllerStorage.new); + app.get(app.locals.BASE_URL + "administration/storages/:id", passport.isAuthenticated, controllerStorage.edit); +}; diff --git a/webapp/routes/api/storage.js b/webapp/routes/api/storage.js new file mode 100644 index 000000000..445d32561 --- /dev/null +++ b/webapp/routes/api/storage.js @@ -0,0 +1,12 @@ + +var passport = require("../../config/Passport"); + +module.exports = function(app) { + var controllerStorage = app.controllers.api.storage; + + app.get(app.locals.BASE_URL + "api/storages/", passport.isAuthenticated, controllerStorage.get); + app.post(app.locals.BASE_URL + "api/storages", passport.isAuthenticated, controllerStorage.save); + app.get(app.locals.BASE_URL + "api/storages/:id", passport.isAuthenticated, controllerStorage.get); + app.put(app.locals.BASE_URL + "api/storages/:id", passport.isAuthenticated, controllerStorage.put); + app.delete(app.locals.BASE_URL + "api/storages/:id", passport.isAuthenticated, controllerStorage.delete); +}; diff --git a/webapp/sockets/TcpSocket.js b/webapp/sockets/TcpSocket.js index c7d836d2c..edc9cccb1 100644 --- a/webapp/sockets/TcpSocket.js +++ b/webapp/sockets/TcpSocket.js @@ -103,7 +103,8 @@ var TcpSocket = function(io) { */ var serviceErrorMessageHandler = function(err){ var errMessageResponse = { - message: "Unknown service error" + message: "Unknown service error", + process: err.process }; if (err){ errMessageResponse.service = err.service; diff --git a/webapp/storage/Logger.js b/webapp/storage/Logger.js new file mode 100644 index 000000000..21dc78c0c --- /dev/null +++ b/webapp/storage/Logger.js @@ -0,0 +1,183 @@ +module.exports = function(port) { + "use strict"; + + // Log library + var winston = require("winston"); + var format = new Date().toLocaleTimeString(); + var fullformat = new Date().toLocaleString(); + var path = require("path"); + var PROJECT_ROOT = path.join(__dirname, '..'); + var NodeFormat = require("util").format; + + //var port = 5000 + + const log_filename = "Storage_" + port + "_terrama2.log"; + const log_filename_debug = "Storage_" + port + "_terrama2_debug.log"; + + /** + * It defines a Winston Logger used in TerraMA² + * @type {winston.Logger} + */ + var logger = new winston.Logger({ + transports: [ + // performs output colorize in console + new (winston.transports.Console)({ + timestamp: format, + colorize: true, + level: process.env.NODE_ENV === 'PRODUCTION' ? 'info' : 'debug', // defining debug to log console + formatter: loggerFormat + }), + new (winston.transports.File)({ + name: 'infofile', + filename:log_filename, + timestamp: fullformat, + colorize: true, + formatter: loggerFormatFile, + level: process.env.NODE_ENV === 'PRODUCTION' ? 'info' : 'error', + json: false + }), + new (winston.transports.File)({ + name: 'debugfile', + filename:log_filename_debug, + timestamp: fullformat, + colorize: true, + formatter: loggerFormatFile, + level: 'debug', + json: false + }) + ], + exitOnError: false + }); + + // TODO: handle exception to a file? + // logger.handleExceptions(new winston.transports.Console({ colorize: true, json: true, formatter: loggerFormat })); + + // keeping logger debug reference + var oldDebug = logger.debug; + // Decorator for logger info + logger.debug = function() { + return oldDebug.apply(this, formatLogArguments(arguments)); + }; + + // keeping logger warning ref + var oldWarning = logger.warn; + // Decorator for logger warning + logger.warn = function() { + return oldWarning.apply(this, formatLogArguments(arguments)); + }; + + // keeping logger error ref + var oldError = logger.error; + // Decorator for logger error + logger.error = function() { + return oldError.apply(this, formatLogArguments(arguments)); + }; + + var myInfo = logger.info; + logger.info = function () { + + return myInfo.apply(this, formatLogArguments(arguments)); + }; + + + /** + * + * It performs a Logger format stream. + * + * @param {any} args - A Winston Logger arguments + * @param {string} args.level - A logger level + * @param {string} args.timestamp - A logger timestamp + * @param {string} args.message - A logger message + * @returns {string} Formatted string to log + */ + function loggerFormat(args) { + format = new Date().toLocaleTimeString(); + return NodeFormat("[%s] %s %s", format, winston.config.colorize(args.level), args.message); + } + + /** + * It performs a Logger format stream. + * + * @param {any} args - A Winston Logger arguments + * @param {string} args.level - A logger level + * @param {string} args.timestamp - A logger timestamp + * @param {string} args.message - A logger message + * @returns {string} Formatted string to log + */ + function loggerFormatFile(args) { + fullformat = new Date().toLocaleString(); + return NodeFormat("[%s][%s] %s", fullformat, args.level, args.message); + } + + /** + * Attempts to add file and line number info to the given log arguments. + * + * @param {...any} args - Logger arguments given + * @return {any[]} + */ + function formatLogArguments (args) { + args = Array.prototype.slice.call(args); + + var stackInfo = getStackInfo(1); + + if (stackInfo) { + // get file path relative to project root + var calleeStr = '(' + stackInfo.relativePath + ':' + stackInfo.line + ')'; + + if (typeof (args[0]) === 'string') { + args[0] = calleeStr + ' ' + args[0]; + } else if (args[0] instanceof Error) { + args[0] = calleeStr + ' ' + args[0].toString() + } else if (args[0] instanceof Object) { + args[0] = calleeStr + ' ' + JSON.stringify(args[0]); + } else { + args.unshift(calleeStr); + } + + for(var i = 1; i < args.length; ++i) { + if (args[0] instanceof Error) { + args[i] = args[i].toString(); + } + + if (args[i] instanceof Object) { + args[i] = JSON.stringify(args[i]); + } + } + } + + return args; + } + + /** + * Parses and returns info about the call stack at the given index. + * + * @param {number} stackIndex - Stack trace index + */ + function getStackInfo (stackIndex) { + // get call stack, and analyze it + // get all file, method, and line numbers + var stacklist = (new Error()).stack.split('\n').slice(3) + + // stack trace format: + // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + // do not remove the regex expresses to outside of this method (due to a BUG in node.js) + var stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi; + var stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi; + + var s = stacklist[stackIndex] || stacklist[0]; + var sp = stackReg.exec(s) || stackReg2.exec(s); + + if (sp && sp.length === 5) { + return { + method: sp[1], + relativePath: path.relative(PROJECT_ROOT, sp[2]), + line: sp[3], + pos: sp[4], + file: path.basename(sp[2]), + stack: stacklist.join('\n') + }; + } + } + + return logger; +}; \ No newline at end of file diff --git a/webapp/storage/README.md b/webapp/storage/README.md new file mode 100644 index 000000000..0893093f2 --- /dev/null +++ b/webapp/storage/README.md @@ -0,0 +1,20 @@ +# Storage Algorithm + +The storage is applied on dynamic data and analysis results. +This data can be tiff files or tables in a database. In case of tables can be multiple tables or a single table. In this way the algorithm has 3 main functions: + +## StoreTIFF +Executes storage over tiff files. + +- Checks in the control chart the date of the last collection or successful analysis, from that date it is calculated to which date the files will be erased as defined by the user +- The date of the tiffs images is in the file, so it is necessary to use the Regex to set the date of these +- Delete or move the files that satisfy the date request +- If the user asks to reset the filter, the history present in the control table is moved to the storage history file. Thus the new collection/analysis is executed as if it had never been executed + +## StoreSingleTable and StoreNTable +Executes storage over tables in the database. The difference between the single and multiple tables is that in the first case the table name and the date attribute are fixed by the TerraMA2 and in the second case they are defined by the user. + +- Check in the control chart the date of the last collection or successful analysis, from that date it is calculated to which date the records will be erased as defined by the user +- The date of the records are defined by a column in the table itself +- Clears or moves records that satisfy the date request +- If the user asks to reset the filter, the history present in the control table is moved to the storage history file. Thus the new collection/analysis is executed as if it had never been executed diff --git a/webapp/storage/storage_core.js b/webapp/storage/storage_core.js new file mode 100644 index 000000000..77a37a731 --- /dev/null +++ b/webapp/storage/storage_core.js @@ -0,0 +1,1116 @@ +"use strict"; + +const fs = require('fs'); +const pg = require('pg'); +const moment = require('moment'); +const Regex = require('xregexp'); +const AdmZip = require('adm-zip'); + +var StatusLog = require("./../core/Enums").StatusLog; +var storageUtils = require("./utils"); + +const analysisTimestampPropertyName = "execution_date"; + + +/** + * performs the backup of the data control execution messages of the dynamic data/analysis + * @param {*} params.storage : storage service is running + * @param {*} params.schema : schema database + * @param {*} params.client : database client + * @param {*} params.logger : logger file + */ +async function backup_Messages(params, service_table, service_type){ + try{ + var sel_process = "SELECT " + service_type + ".id FROM \ + " + params.schema + "." + service_type +", " + params.schema + ".storages WHERE \ + " + service_type + ".data_series_output = \'" + params.storage.data_series_id + "\' AND \ + storages.id = \'" + params.storage.id + "\'"; + //console.log(params.storage.name, sel_process); + var res = await params.client.query(sel_process); + if (res.rowCount){ + + var process_id = res.rows[0].id; + + //Copy messages from process to storage_historics + params.logger.debug(params.storage.name + ": Copying messages from process to storage_historics"); + var sql_insert = "INSERT INTO " + params.schema + ".storage_historics (process_id, status, start_timestamp, data_timestamp, last_process_timestamp, data, \ + origin, type, description, storage_id) SELECT \ + a.process_id, a.status, a.start_timestamp, a.data_timestamp, a.last_process_timestamp, \ + a.data, \'" + service_type + "\', b.type, b.description, \'" + params.storage.id + "\' FROM \ + " + params.schema + "." + service_table + " a, " + params.schema + "." + service_table + "_messages b, \ + " + params.schema + "." + service_type + " c, " + params.schema + ".storages d\ + WHERE b.log_id = a.id AND \ + a.process_id = \'" + process_id + "\'"; + var res = await params.client.query(sql_insert); + + if (!res.rowCount){ //Don't have messages, so never have error, brings only data + var sql_insert1 = "INSERT INTO " + params.schema + ".storage_historics (\ + process_id, status, start_timestamp, data_timestamp, last_process_timestamp, data, \ + origin, storage_id) SELECT \ + a.process_id, a.status, a.start_timestamp, a.data_timestamp, a.last_process_timestamp, \ + a.data, \'" + service_type + "\', \'" + params.storage.id + "\' FROM \ + " + params.schema + "." + service_table + " a WHERE a.process_id = \'" + process_id + "\'"; + params.client.query(sql_insert1); + + } +//Delete messages from collector_messages or analysis_messages + var sql_remove_messages = "DELETE FROM " + params.schema + "." + service_table + "_messages USING \ + " + params.schema + "." + service_table + " WHERE \ + " + service_table + ".id = " + service_table + "_messages.log_id AND \ + " + service_table + ".process_id = \'" + process_id + "\'"; + var res1 = await params.client.query(sql_remove_messages); + +//Delete process from collector or analysis + var sql_remove_process = "DELETE FROM " + params.schema + "." + service_table + " WHERE \ + " + service_table + ".process_id = \'" + process_id + "\'"; + var res2 = params.client.query(sql_remove_process); + + if (res.rowCount){ + params.logger.log('info', params.storage.name + ": Added " + res.rowCount + " rows in storage_historics"); + params.logger.log('info', params.storage.name + ": Removed " + res1.rowCount + " rows from " + service_table + "_messages"); + params.logger.log('info', params.storage.name + ": Removed " + res2.rowCount + " rows from " + service_table); + } + else{ + params.logger.log('info', params.storage.name + ": None messages from process to moved to storage_historics"); + } + } + } + catch(err) + { + throw err; + } +}; + +/** + * Moves a file from one directory to another + * @param {*} oldPath : source path + * @param {*} newPath : destiny path + */ + function moveFile(oldPath, newPath) { + + fs.rename(oldPath, newPath, function (err) { + if (err) { + if (err.code === 'EXDEV') { + copy(); + } else { + throw err; + } + return; + } + }); + + function copy() { + var readStream = fs.createReadStream(oldPath); + var writeStream = fs.createWriteStream(newPath); + + readStream.on('error', callback); + writeStream.on('error', callback); + + readStream.on('close', function () { + fs.unlink(oldPath, callback); + }); + + readStream.pipe(writeStream); + } +}; + +/** + * Adds the values ​​in the backup table + * @param {*} uri data provider + * @param {*} table_name backup table name + * @param {*} params parameters for new table + * @param {*} params.storage storage parameters + * @param {*} params.logger logger file + * @param {*} params.values values to insert in new table + * @param {*} params.columns columns copied + * @param {*} params.key key for new table + * @param {*} params.seq sequence for new table + */ +async function insertTable(uri, table_name, params){ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + pg.connect(uri, async (err, client, done) =>{ + + var date_attr; + var create_table; + + if (params.seq){ + var create_seq = "CREATE SEQUENCE IF NOT EXISTS " + params.seq.sequence_name + " \ + INCREMENT " + params.seq.increment + " \ + MINVALUE " + params.seq.minimum_value + " \ + MAXVALUE " + params.seq.maximum_value + " \ + START " + params.seq.start_value + " \ + CACHE 1"; + + // console.log(params.storage.name,create_seq); + var sequence = await client.query(create_seq); + + create_table = "CREATE TABLE IF NOT EXISTS " + table_name + " (" + + for (let column of params.columns) { + create_table += column.column_name; + if (column.data_type == 'USER-DEFINED'){ + create_table += " " + column.udt_name + "(" + column.type + "," + column.srid + ")"; + } + else{ + create_table += " " + column.data_type; + if (column.data_type == 'character varying') + create_table += "(" + column.character_maximum_length + ")" + if (!column.is_nullable) + create_table += " NOT NULL"; + if (column.column_default) + create_table += " DEFAULT " + column.column_default; + } + create_table += ", "; + } + create_table += "CONSTRAINT " + params.key.constraint_name + " \ + PRIMARY KEY (" + params.key.column_name + "))"; + } + else{ + create_table = "CREATE TABLE IF NOT EXISTS " + table_name + " (" + + for (let column of params.columns) { + create_table += column.column_name + " " + column.type; + if (column.notNull) + table_name += " NOT NULL"; + if (column.default) + table_name += " DEFAULT " + column.default; + create_table += ", "; + } + + } + + //console.log(params.storage.name,create_table); + params.logger.debug(params.storage.name + ": Create table if not exists => " + table_name) + var rows_added = 0; + var rows_read = 0; + client.query(create_table, async (err1, res1) => { + if (err1){ + throw err1; + } + var insert = "INSERT INTO " + table_name + "(" + Object.keys(params.values.rows[0]) + ") VALUES ("; + //console.log(params.storage.name,insert); + for await (let row of params.values.rows){ + var insert_values = insert; + var row_values = Object.values(row); + for await (let val of row_values){ + if (Object.prototype.toString.call(val)=== '[object Date]') + insert_values += "\'" + moment(val).format('YYYY-MM-DD HH:mm:ss') + "\',"; + else if (val !== null){ + if (typeof(val) === 'string') + val = val.replace("\'", "\'\'") + insert_values += "\'" + val + "\',"; + } + else + insert_values += "null,"; + } + var n = insert_values.length; + insert_values = insert_values.slice(0, n-1); //remove last ',' + insert_values += ") ON CONFLICT DO NOTHING"; + //console.log(params.storage.name,insert_values); + var res2 = await client.query(insert_values); + rows_added += res2.rowCount; + } + params.logger.log('info', params.storage.name + ": " + rows_added + " rows added in table " + table_name); + return resolve(true); + }); + }); + // return resolve(false); + } + catch(err){ + params.looger.error(params.storage.name + ": " + err); + return reject(err); + } + }); +}; + +/** + * Selects the input data type and corresponding control table (collector or analysis) + */ +async function selectServiceInput (params) +{ + return new Promise(async function resolvPromise(resolve, reject){ + const is_dynamicData = "SELECT service_instance_id FROM \ + " + params.schema + ".collectors WHERE data_series_output = " + params.storage.data_series_id; + const is_analysis = "SELECT instance_id FROM \ + " + params.schema + ".analysis, " + params.schema + ".data_sets, " + params.schema + ".data_series WHERE \ + analysis.dataset_output = data_sets.id AND data_series.id = " + params.storage.data_series_id; + + var service_table ; + var service_type; + + try{ + var res = await params.client.query(is_dynamicData); + + if (res.rowCount){ + service_type = "collectors" + service_table = "collector_" + res.rows[0].service_instance_id; + } + else{ + var res1 = await params.client.query(is_analysis); + service_type = "analysis" + service_table = "analysis_" + res1.rows[0].instance_id; + } + return resolve({service_table, service_type}); + } + catch(e){ + params.logger.error(params.storage.name + ": " + e); + return reject(e); + } + }); + +}; + +/** + * Executes the storage in type data: + * "DCP-postgis", "ANALYSIS_MONITORED_OBJECT-postgis", "OCCURRENCE-postgis" + * Multiple tables + * @param { + * clientSocket terraMA2 application client + * schema terrama2 database schema + * storage storage + * client database client, + * logger logger file + * } params + * @param {*} res.uri data provider where to backup + * @param {*} res.value backup table name + * @param {*} res.id dataset id + */ +async function StoreNTable_1(params, res){ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + for (var row of res.rows){ + var table_name_back = params.storage.uri.slice(params.storage.uri.lastIndexOf("/")+1, params.storage.uri.length); + if (res.rowCount > 1) + table_name_back += "_" + row.value; + + var timestamp = ""; + var geometry = ""; + + //If is monitored object is need to know what the date and geometry attributes + if (params.storage.data_serie_code != "ANALYSIS_MONITORED_OBJECT-postgis"){ + var sel_timestamp = "SELECT data_set_formats.value FROM \ + " + params.schema + ".data_set_formats WHERE \ + data_set_formats.key = \'timestamp_property\' AND \ + data_set_formats.data_set_id = \'" + row.id + "\'"; + + var sel_geom = "SELECT data_set_formats.value FROM \ + " + params.schema + ".data_set_formats WHERE \ + data_set_formats.key = \'geometry_property\' AND \ + data_set_formats.data_set_id = \'" + row.id + "\'"; + + //Gets a attribute name of DataTime + var res1 = await params.client.query(sel_timestamp); + //Gets a attribute name of GeometryData + var res2 = await params.client.query(sel_geom); + + if (res1.rowCount) + timestamp = res1.rows[0].value; + if (res2.rowCount) + geometry = res2.rows[0].value; + + params.logger.debug(params.storage.name + ": Timestamp and geometry properties => " + timestamp + ", " + geometry); + } + else + timestamp = analysisTimestampPropertyName; //"execution_date" + + var paramsout = { + storage : params.storage, + schema : params.schema, + client : params.client, + logger : params.logger, + table_name : row.value, + database_project : row.uri, + table_name_back : table_name_back, + timestamp_prop : timestamp, + geometry_prop: geometry + }; + + var paramsreturn = await StoreTable(paramsout); + } + + if (paramsreturn){ + params.storage.process.last_process_timestamp = moment().format(); + params.storage.process.data_timestamp = paramsreturn.data_timestamp; + params.storage.process.description = paramsreturn.description; + params.storage.process.status = paramsreturn.status; + + updateMessages(params); + } + + return resolve(); + } + catch(e){ + return reject(e); + } + }); + +}; + +/** + * Erase data from the table and save it to the backup table + * @param { + * storage : storage + * schema : schema + * client : client + * logger : logger + * table_name : value + * database_project : uri + * table_name_back : table_name_back + * timestamp_prop : timestamp + * geometry_prop: geometry +* } params + */ +async function StoreTable(params) +{ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + var database_proj_name = params.database_project.slice(params.database_project.lastIndexOf("/")+1, params.database_project.length); + + var res_data = await getDataUntilStore(params); + + var data_timestamp; + //if filter, erase all messages to collect/analysis all again, from filter origin date + if (params.storage.filter) + backup_Messages(params, res_data.service_table, res_data.service_type); + + params.logger.log('info', params.storage.name + ": Removing data before " + res_data.data_until_store.format('YYYY-MM-DD HH:mm:ss') + " from " + params.table_name); + + //Connect to database where data will be storaged + pg.connect(params.database_project, async (err6, client_proj, done) =>{ + if(err6) { + params.logger.error(params.storage.name + ": " + err6); + throw err6; + } + + //Attributes to be included in newtable, key is serial, so it will not be included + var sel_columns = "SELECT * FROM information_schema.columns WHERE table_name = \'" + params.table_name + "\'"; + var res_columns = await client_proj.query(sel_columns, function (err7){ + if (err7) + return reject(err7); + }); + var columns_to_be_back = ""; + for (var column of res_columns.rows){ + if (!column.column_default) + columns_to_be_back += column.column_name + ", "; + } + + columns_to_be_back = columns_to_be_back.slice(0,columns_to_be_back.lastIndexOf(",")); + + var sql_delete = "DELETE FROM " + params.table_name + " WHERE \ + " + params.table_name + "." + params.timestamp_prop + " < \'" + res_data.data_until_store.format('YYYY-MM-DD HH:mm:ss') + "\' \ + RETURNING " + columns_to_be_back; + //params.logger.debug(params.storage.name,sql_delete); + + var res_del = await client_proj.query(sql_delete, function (err){ + if (err) + return reject(err); + }); + + if (!res_del.rowCount){ + params.logger.debug(params.storage.name, "None registers to delete from "+ params.table_name); + var obj = { + flag: false, + data_timestamp: "", + description : "No data to process", + status: StatusLog.WARNING + }; + + return resolve(obj); + } + + var uri; + + var row_values = Object.values(res_del.rows[res_del.rowCount-1]); + for await (let val of row_values){ + if (Object.prototype.toString.call(val)=== '[object Date]'){ + data_timestamp = val; + break; + } + } + + if (params.storage.backup){ + var sql_data_provider_out = "SELECT data_providers.uri FROM \ + " + params.schema+".data_providers," + params.schema + ".storages WHERE \ + data_providers.id = \'" + params.storage.data_provider_id+"\' AND \ + storages.id = \'" + params.storage.id + "\'"; + + var data_provider_out = await params.client.query(sql_data_provider_out); + uri = data_provider_out.rows[0].uri; + + //To verify if table contains geometric information + params.logger.debug(params.storage.name + ": Verifying if table contains geometric information => "+ params.table_name); + var selecttypes = "SELECT \ + g.column_name, \ + g.column_default, \ + g.is_nullable, \ + g.data_type, \ + g.character_maximum_length, \ + g.udt_name, \ + f.type, \ + f.srid FROM \ + information_schema.columns as g JOIN \ + geometry_columns AS f \ + ON (g.table_schema = f.f_table_schema and g.table_name = f.f_table_name ) \ + WHERE table_name = \'" + params.table_name + "\'"; + + var res3 = await client_proj.query(selecttypes); + + var table; + var params_newtable = {}; + params_newtable.storage = params.storage; + params_newtable.values = res_del; + params_newtable.logger = params.logger; + var seq_name; + + if (res3.rowCount){ + params_newtable.columns = res3.rows; + } + else{ + selecttypes = "SELECT \ + g.column_name, \ + g.column_default, \ + g.is_nullable, \ + g.data_type, \ + g.character_maximum_length, \ + g.udt_name FROM \ + information_schema.columns as g \ + WHERE table_name = \'" + params.table_name + "\'"; + + var res_col = await client_proj.query(selecttypes); + if (res_col.rowCount) + params_newtable.columns = res_col.rows; + else{ + params.logger.error("Returned 0 => " + selecttypes); + throw ("Table " + params.table_name + " with problems!"); + } + } + + for (var row in params_newtable.columns){ + if (params_newtable.columns[row].column_default){ + seq_name = params_newtable.columns[row].column_default.slice(params_newtable.columns[row].column_default.indexOf("(")+1, params_newtable.columns[row].column_default.indexOf(":")) + params_newtable.columns[row].column_name = params_newtable.columns[row].column_name.replace(params.table_name, params.table_name_back); + var re = new RegExp(params.table_name, 'g'); //to replace all references + params_newtable.columns[row].column_default = params_newtable.columns[row].column_default.replace(re, params.table_name_back); + } + } + + params.logger.debug(params.storage.name + ": Creating key and sequence in backup table => "+ params.table_name_back); + var select_key = "SELECT column_name, constraint_name FROM information_schema.constraint_column_usage WHERE table_name = \'" + params.table_name + "\'"; + var select_seq = "SELECT sequence_name, increment, minimum_value, maximum_value, start_value FROM information_schema.sequences WHERE sequence_name = " + seq_name + ""; + + var res4 = await client_proj.query(select_key); + var res5 = await client_proj.query(select_seq); + + if (res4.rowCount){ + params_newtable.key = res4.rows[0]; + params_newtable.key.column_name = params_newtable.key.column_name.replace(params.table_name, params.table_name_back); + params_newtable.key.constraint_name = params_newtable.key.constraint_name.replace(params.table_name, params.table_name_back); + } + + if (res5.rowCount){ + params_newtable.seq = res5.rows[0]; + params_newtable.seq.sequence_name = params_newtable.seq.sequence_name.replace(re, params.table_name_back); + } + + var res_insert_table = await insertTable(uri, params.table_name_back, params_newtable); + } + + if (data_timestamp) + params.storage.process.data_timestamp = data_timestamp ; + + params.logger.log('info', params.storage.name + ": " + res_del.rowCount + " rows removed from " + params.table_name); + + var obj = { + flag: true, + deleted_register: res_del.rowCount, + table: params.table_name, + data_timestamp: params.storage.process.data_timestamp, + status: StatusLog.DONE + }; + + return resolve(obj); + + }); + } + catch(err){ + params.logger.error(params.storage.name + ": " + err); + return reject(err); + } + }); +}; + +/** + * Updates the collector_* or analysis_* and collector_messages or analysis_messages tables + * @param {*} params + */ +async function updateMessages(params){ + params.logger.debug (params.storage.name, ": Updating messages in storages table messages"); + return new Promise(async function resolvePromise(resolve, reject){ + try{ + + var service_table = "storage_" + params.storage.service_instance_id; + var service_table_message = service_table + "_messages"; + + var insertvalues; + var start = moment(params.storage.process.start_timestamp).format('YYYY-MM-DD HH:mm:ss'); + var last = moment(params.storage.process.last_process_timestamp).format('YYYY-MM-DD HH:mm:ss'); + if (params.storage.process.data_timestamp){ + var data_time = moment(params.storage.process.data_timestamp).format('YYYY-MM-DD HH:mm:ss'); + insertvalues = "INSERT INTO " + params.schema + "." + service_table + + " (process_id, status, start_timestamp, data_timestamp, last_process_timestamp, data) VALUES(" + + params.storage.id + ", " + params.storage.process.status + ", " + "\'" + start + "\', \'" + data_time + + "\', \'" + last + "\', \'{\"processing_end_time\":[\"" + moment(params.storage.process.last_process_timestampl).format('YYYY-MM-DDTHH:mm:ssZ') + + "\"],\"processing_start_time\":[\"" + moment(params.storage.process.start_process_timestampl).format('YYYY-MM-DDTHH:mm:ssZ') + "\"]}\')"; + } + else{ + insertvalues = "INSERT INTO " + params.schema + "." + service_table + + " (process_id, status, start_timestamp, last_process_timestamp) VALUES(" + + params.storage.id + "," + params.storage.process.status + ", \'" + start + "\', \'" + last + "\') RETURNING *"; + } + + //params.logger.debug(params.storage.name,insertvalues); + + params.client.query(insertvalues, async (err, res) =>{ + if (err){ + params.logger.error(params.storage.name + " => " + insertvalues + " => " + err); + throw err; + } + if (!params.storage.process.data_timestamp){ + params.storage.process.description = params.storage.process.description ? params.storage.process.description : " "; + var insert_msg = "INSERT INTO " + params.schema + "." + service_table_message + + " (log_id, type, description, timestamp) VALUES(" + res.rows[0].id + ", " + + params.storage.process.status + ", \'" + params.storage.process.description + "\', \'" + last + "\')"; + //params.logger.debug(params.storage.name,insert_msg); + params.client.query(insert_msg); + } + return resolve(true); + }); + } + catch(e){ + params.logger.error(params.storage.name + ": " + e); + return reject(e); + } + }); +}; + +async function getMessages(client, service_table_message, id){ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + var selec_messages = "SELECT * FROM " + service_table_message + " WHERE log_id = \'" + id + "\'"; + + var res = await client.query(selec_messages); + var messages = []; + if (res.rowCount){ + var row = res.rows[0]; + var msg = { + id : row.id, + log_id : row.log_id, + type : row.type, + description : row.description, + timestamp : row.timestamp + } + messages.push(msg); + } + + return resolve(messages); + } + catch(err){ + console.log(err); + return reject(err); + } + }); +}; + + +async function getProcessLog(client, selec_log, service_table_message){ + return new Promise(async function resolvePromise(resolve, reject){ + + try{ + var logs = []; + var res = await client.query(selec_log); + for await (const row of res.rows){ + var log ={ + id : row.id, + process_id : row.process_id, + status : row.status, + start_timestamp : row.start_timestamp, + data_timestamp : row.data_timestamp, + last_process_timestamp : row.last_process_timestamp, + data : row.data + } + + var messages = await getMessages(client, service_table_message, log.id); + log.messages = messages; + logs.push(log); + } + return resolve(logs); + } + catch(err){ + console.log(err); + return reject(err); + } + }); +}; + +/** + * Return until date to remove + * @param { + * logger: logger file + * schema: terrama2 database schema + * storage: storage service + * } params + */ +async function getDataUntilStore(params){ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + + params.logger.debug(params.storage.name + ": Getting last success date collected/analyzed") + var res_1 = await selectServiceInput(params); + + var service_table = res_1.service_table; + var service_type = res_1.service_type; + + var sel_date; + if (service_type === "collectors"){ + sel_date = "SELECT MAX(data_timestamp) FROM \ + " + params.schema + "." + service_table + ", \ + " + params.schema + "." + service_type + " WHERE \ + " + service_table + ".process_id = " + service_type + ".id AND \ + " + service_type + ".data_series_output = " + params.storage.data_series_id; + } + else{ + sel_date = "SELECT MAX(data_timestamp) FROM \ + " + params.schema + "." + service_table + ", \ + " + params.schema + "." + service_type + ", \ + " + params.schema + ".data_sets WHERE \ + " + service_table + ".process_id = " + service_type + ".id AND \ + " + service_type + ".dataset_output = data_sets.id AND data_sets.data_series_id = " + params.storage.data_series_id; + } + + var res_data = await params.client.query(sel_date); + params.logger.debug(params.storage.name + ": max data_time_stamp " + res_data.rows[0].max); + + var data_until_store = res_data.rowCount ? new moment(res_data.rows[0].max) : new moment(); + data_until_store = data_until_store._isValid ? data_until_store : new moment(); + + // if keep_data equal zero, erases everything + if (params.storage.keep_data > 0){ + data_until_store.subtract(params.storage.keep_data, params.storage.keep_data_unit); + } + else //erases all + data_until_store = new moment(); + + var res = { + service_table : service_table, + service_type : service_type, + data_until_store : data_until_store + } + return resolve(res); + + } + catch(err){ + return reject(err); + } + }); +} + + +module.exports = { +/** + * Createa a message control table of storages in terrama2 database + * @param {*} client : database client + * @param {*} schema : schema database + * @param {*} storage : storage service to run + */ + createMessageTable: async function (params) + { + try{ + var service_table = "storage_" + params.storage.service_instance_id; + var service_table_message = service_table + "_messages"; + + var createtable = "CREATE TABLE IF NOT EXISTS " + params.schema + "." + service_table + " (\ + id serial NOT NULL,\ + process_id integer NOT NULL,\ + status integer NOT NULL,\ + start_timestamp timestamp(1) with time zone,\ + data_timestamp timestamp with time zone,\ + last_process_timestamp timestamp(1) with time zone,\ + data text,\ + CONSTRAINT " + service_table + "_pk PRIMARY KEY (id))"; + + var createtable_messages = "CREATE TABLE IF NOT EXISTS "+ params.schema + "." + service_table_message + " ( \ + id serial NOT NULL,\ + log_id integer NOT NULL,\ + type integer NOT NULL,\ + description text,\ + \"timestamp\" timestamp(1) with time zone,\ + CONSTRAINT " + service_table_message +"_pk PRIMARY KEY (id),\ + CONSTRAINT " + service_table_message +"_fk FOREIGN KEY (log_id)\ + REFERENCES " + params.schema + "." + service_table +"(id) MATCH SIMPLE\ + ON UPDATE NO ACTION ON DELETE NO ACTION)"; + + //console.log(params.storage.name,createtable); + //console.log(params.storage.name,createtable_messages); + var res1 = await params.client.query(createtable); + var res2 = await params.client.query(createtable_messages); + } + catch(e){ + params.logger.error(e); + throw e; + } + }, + + + /** + * To backup/erase GRID-geotif + * Delete the files according to the last valid collection / analysis, that is, + * the data_timestamp of the message control table + */ + StoreTIFF: async function (params) + { + return new Promise(async function resolvePromise(resolve, reject){ + const select_sql = "SELECT service_types.name, \ + service_types.id, \ + data_providers.uri, \ + data_set_formats.value FROM \ + " + params.schema + ".service_types, \ + " + params.schema + ".service_instances, \ + " + params.schema + ".data_providers, \ + " + params.schema + ".data_series, \ + " + params.schema +".data_sets, \ + " + params.schema +".data_set_formats, \ + " + params.schema + ".storages \ + WHERE data_sets.data_series_id = data_series.id AND \ + data_set_formats.data_set_id = data_sets.id AND \ + data_set_formats.key = 'mask' AND \ + data_series.data_provider_id = data_providers.id AND \ + service_instances.id = \'" + params.storage.service_instance_id + "\' AND \ + service_instances.service_type_id = service_types.id AND \ + data_series.id = \'" + params.storage.data_series_id +"\'"; + + params.storage.process = {}; + params.storage.process.start_timestamp = moment(); + // server.emit('startService'); + console.log('Executing ' + params.storage.name); + params.logger.log('info', "Initializing execution of " + params.storage.name); + + //last valid date of processed data + var data_timestamp; + + try{ + var res1 = await params.client.query(select_sql); + + //Get date of data of last success tiff generation + var res_data_tiff = await getDataUntilStore(params); + + //Get date of data of last success storage + var service_table_storage = "storage_" + params.storage.service_instance_id; + var sel_date_storage = "SELECT MAX(data_timestamp) FROM " + params.schema + "." + service_table_storage + " WHERE process_id = " + params.storage.id; + var res_data_storage = await params.client.query(sel_date_storage); + + //if filter, erase all messages to collect/analysis all again, from filter origin date + if (params.storage.filter) + backup_Messages(params, res_data_tiff.service_table, res_data_tiff.service_type); + + var dateObj = new Date(res_data_storage.rows[0].max); + var momentObj = moment(dateObj); + + //test if has data to process, that is, if the data was processed after the last storage + if (momentObj.isAfter(res_data_tiff.data_until_store)){ + var obj = { + flag: false, + data_out: data_out, + data_timestamp: "", + description : "No data to process" + }; + + return resolve(obj); + } + + params.logger.log('info', params.storage.name + ": Removing data before " + res_data_tiff.data_until_store.format('YYYY-MM-DD HH:mm:ss')); + + //console.log("storage " + params.storage.name + " data " + JSON.stringify(res_data_tiff.data_until_store, null, 4)); + + var uri = res1.rows[0].uri; + var mask = res1.rows[0].value; + var path = uri.slice(uri.indexOf("//") +2, uri.lenght) + "/" + mask.slice(0,mask.indexOf("/")); + var regexString = await storageUtils.terramaMask2Regex( mask.slice(mask.indexOf("/")+1, mask.lenght)); + const regex = new Regex(regexString); + + var data_out = params.storage.uri; + + if (params.storage.zip){ + data_out = data_out.slice(params.storage.uri.indexOf("//")+2, params.storage.uri.lenght) + "/" + mask.slice(0,mask.indexOf("%")); + var zip = new AdmZip(); + } + else{ + data_out = data_out.slice(params.storage.uri.indexOf("//")+2, params.storage.uri.lenght) + "/" + mask.slice(0,mask.indexOf("/")); + } + + var moved_files = 0; + var deleted_files = 0; + await fs.readdir(path, async function(err4, files){ + if (err4){ + params.logger.error(params.storage.name + ": " + err4); + throw err4; + } + for (var file of files){ + var match = regex.exec(file); + //test if filename match with mask + if (match){ + var year_file = new Number(match[1]); + var year_proc = res_data_tiff.data_until_store.year(); + var century = year_proc - year_file; + // If the year is represented by 2 digits it is necessary to know which century. + // I considered only the 20th (1900) and 21st (2000) centuries + if (century > 1999) + year_file = year_file +2000; + else if (century > 1899) + year_file = year_file + 1900; + if (match[4]=== null) + match[4] = 0; + if (match[5]=== null) + match[5] = 0; + + var filedate = new moment([year_file, Number(match[2])-1, Number(match[3]), Number(match[4]), Number(match[5])]); + + if (!data_timestamp) + data_timestamp = filedate; + else + data_timestamp = filedate.isBefore(data_timestamp) ? filedate : data_timestamp; + + if (filedate.isBefore(res_data_tiff.data_until_store)){ + if (params.storage.backup === true){ + if (params.storage.zip){ + zip.addLocalFile(path + "/" + file); + fs.unlink(path + "/" + file, function(err6){ + if (err6){ + params.logger.error(params.storage.name + ": " + err6); + throw err6; + } + deleted_files++; + }); + } + else{ + if (!fs.existsSync(data_out)){ + fs.mkdirSync(data_out, {recursive:true}); + } + + moveFile(path + "/" + file, data_out + "/" + file); + } + moved_files++; + } + else{ + fs.unlink(path + "/" + file, function(err6){ + if (err6){ + params.logger.error(params.storage.name + ": " + err6); + throw err6; + } + deleted_files++; + }); + } + } + } + } + + if (params.storage.zip){ + var maskzip = mask.slice(mask.indexOf("%"), mask.indexOf(".")); + maskzip = maskzip.replace(/%/g,''); + var zipfile = data_out + moment().format(maskzip) + ".zip"; + zip.writeZip(zipfile); + } + + params.storage.process.status = StatusLog.DONE; + params.storage.process.data_timestamp = data_timestamp; + params.storage.process.last_process_timestamp = moment(); + + updateMessages(params); + + var obj = { + flag: true, + moved_files: moved_files, + deleted_files: deleted_files, + path: path, + data_out: data_out, + data_timestamp: data_timestamp, + zipfile : zipfile + }; + + return resolve(obj); + }); + } + catch(err){ + params.logger.error(params.storage.name + ": " + err); + params.storage.process.last_process_timestamp = moment(); + params.storage.process.status = StatusLog.ERROR; + params.storage.process.description = err; + updateMessages(params); + return reject(err); + } + }); + }, + +/** + * Executes the storage in type data "DCP-single_table" + * Single table + * @param { + * clientSocket terraMA2 application client + * schema terrama2 database schema + * storage storage + * client database client, + * logger logger file + * } params + */ +StoreSingleTable: async function (params) + { + return new Promise(async function resolvePromise(resolve, reject){ + //server.emit('startService'); + + params.storage.process = {}; + params.storage.process.start_timestamp = moment(); + + var table_name = "dcp_data_" + params.storage.data_series_id; + + const select_sql = "SELECT service_types.name, service_types.id, data_providers.uri FROM \ + " + params.schema + ".service_types, \ + " + params.schema + ".service_instances, \ + " + params.schema + ".storages, \ + " + params.schema + ".data_series, \ + " + params.schema + ".data_providers WHERE \ + data_providers.id = data_series.data_provider_id AND \ + data_series.id = \'" + params.storage.data_series_id + "\' AND \ + service_types.id = service_instances.service_type_id AND \ + service_instances.id = \'" + params.storage.service_instance_id + "\' AND \ + storages.id = \'" + params.storage.id + "\'"; + console.log(params.storage.name,'Executing ' + params.storage.name); + params.logger.log('info', "Initializing execution of " + params.storage.name); + + try{ + var res1 = await params.client.query(select_sql); + + var params1 = { + storage : params.storage, + schema : params.schema, + client : params.client, + logger: params.logger, + table_name : table_name, + database_project : res1.rows[0].uri, + table_name_back : params.storage.uri.slice(params.storage.uri.lastIndexOf("/")+1, params.storage.uri.length), + timestamp_prop : 'datetime' + }; + + var res_storage = await StoreTable(params1); + + params.logger.log('info', "Finalized execution of " + params.storage.name); + params.storage.process.data_timestamp = res_storage.data_timestamp; + params.storage.process.last_process_timestamp = moment(); + params.storage.process.status = StatusLog.DONE; + params.storage.process.description = res_storage.description; + updateMessages(params); + + return resolve(); + } + catch(err){ + params.logger.error(params.storage.name + ": " + err); + params.storage.process.last_process_timestamp = moment(); + params.storage.process.status = StatusLog.ERROR; + params.storage.process.description = err; + updateMessages(params); + return reject(err); + } + }); + }, + +/** + * Executes the storage in type data: + * "DCP-postgis", "ANALYSIS_MONITORED_OBJECT-postgis", "OCCURRENCE-postgis" + * Multiple tables + * @param { + * clientSocket terraMA2 application client + * schema terrama2 database schema + * storage storage + * client database client, + * logger logger file + * } params + */ + StoreNTable: async function (params) + { + return new Promise(async function resolvePromise(resolve, reject){ + // server.emit('startService'); + params.storage.process = {}; + params.storage.process.start_timestamp = moment(); + + try{ + + params.logger.log('info', "Initializing execution of " + params.storage.name); + + const select_sql = "SELECT data_providers.uri, data_set_formats.value, data_sets.id FROM \ + " + params.schema + ".storages, \ + " + params.schema + ".data_series, \ + " + params.schema+".data_sets, \ + " + params.schema+".data_set_formats, \ + " + params.schema + ".data_providers WHERE \ + data_series.id = data_sets.data_series_id AND \ + data_set_formats.data_set_id = data_sets.id AND \ + data_set_formats.key = 'table_name' AND \ + data_providers.id = data_series.data_provider_id AND \ + data_series.id = \'" + params.storage.data_series_id + "\' AND \ + storages.id = \'" + params.storage.id + "\'"; + + params.client.query(select_sql, async (err, res) => { + if (err){ + throw err; + } + + await StoreNTable_1(params, res); + + return resolve(); + + }); + } + catch(err){ + params.logger.error(params.storage.name + ": " + err); + params.storage.process.last_process_timestamp = moment(); + params.storage.process.status = StatusLog.ERROR; + params.storage.process.description = err; + updateMessages(params); + return reject(err); + } + }); + }, + + getLogs: async function(client, schema, service_instance_id, process_ids, begin, end){ + return new Promise(async function resolvePromise(resolve, reject){ + if(begin > end) + swap(begin, end); + + var rowNumbers = (end - begin) + 1; + + var service_table = "storage_" + service_instance_id; + var service_table_message = schema + "." + service_table + "_messages"; + var objs=[]; + + for await (var process_id of process_ids){ + var selec_log = "SELECT * FROM " + schema + "." + service_table + " WHERE process_id = \'" + process_id + "\' \ + ORDER BY id DESC LIMIT \'" + rowNumbers + "\' OFFSET \'" + begin + "\'"; + //console.log(selec_log); + + await getProcessLog(client, selec_log, service_table_message) + .catch(err => { + console.log(err); + return reject(err); + }) + .then (logs => { + var obj ={ + instance_id : service_instance_id, + process_id : process_id, + log : logs + } + objs.push(obj); + }); + } + return resolve(objs); + }); + } +} diff --git a/webapp/storage/storage_service.js b/webapp/storage/storage_service.js new file mode 100755 index 000000000..c486b0380 --- /dev/null +++ b/webapp/storage/storage_service.js @@ -0,0 +1,737 @@ +#!/usr/bin/env node + +'use strict'; + +/** + * It defines which context nodejs should initialize. The context names are available in {@link config/instances} + * @typeof {string} + */ + +//console.log("No storage_Service.js", process.argv); + +var nodeContext = "";//process.argv[1]; +//console.log('nodeContext ' +nodeContext ); + +/** + * It defines TerraMA² global configuration (Singleton). Make sure it initializes before everything + * + * @type {Application} + */ +var Application = require("./../core/Application"); + +// setting currentContext +Application.setCurrentContext(nodeContext); + +/** + * Module dependencies. +// */ +const net = require('net'); +const Sequelize = require('sequelize'); +const pg = require('pg'); +const Regex = require('xregexp'); +const moment = require('moment'); +const CronJob = require('cron').CronJob; + +var PortScanner = require("./../core/PortScanner"); +var io = require('../io'); +var debug = require('debug')('webapp:server'); +var load = require('express-load'); +var TcpService = require("./../core/facade/tcp-manager/TcpService"); +var TcpManager = require("./../core/TcpManager"); +var Utils = require('./../core/Utils'); +var Signals = require('./../core/Signals'); +var ServiceType = require("./../core/Enums").ServiceType; +var StatusLog = require("./../core/Enums").StatusLog; +var ScheduleType = require("./../core/Enums").ScheduleType; +var storageUtils = require('./utils'); +var sto_core = require('./storage_core'); + +var portNumber = '3600'; //TerraMA2 default port +var nodeContextPort = '5000'; //storage default port + +let version = Application.get().metadata.version; + +for (var v of process.argv){ + if (v === '--version'){ + console.log(version); + process.exit(0); + } + if (!isNaN(v)) + nodeContextPort = v; +} + +/** + * It defines which port nodejs should use. When this variables is used, it overrides port number from {@link config/instances} + */ +//var nodeContextPort = process.argv[3]; +console.log('nodeContextPort ' + nodeContextPort); + +var app = require('../app'); + +// storing active connections +var connections = {}; +let settings = Application.getContextConfig(); + +let contextConfig = settings.db; + +console.log("contextConfig",contextConfig); + +var debugMessage = ""; +let schema = contextConfig.define.schema; // getting terrama2 schema +let databaseName = contextConfig.database; // getting terrama2 database name + +if (PortScanner.isValidPort(Number(nodeContextPort))) { + portNumber = nodeContextPort; +} else { + debugMessage = "No valid port found. The default port " + portNumber + " will be used"; +} + +/** + * Get port from environment and store in Express. + */ +let port = storageUtils.normalizePort(process.env.PORT || portNumber); + +/** + * It defines Storage logger library + * @type {winston.Logger} + */ +var logger = require("./Logger")(port); + +/** + * Create server. + */ +const server = net.createServer(); + +if (debugMessage) { + console.log(debugMessage); +} + +/* +* Storages instances array +*/ +let Storages = []; + +/* +* List with cropjobs with schedule to run storage +*/ +let joblist = []; + +/** + * Executes a storage + * @param {*} clientSocket : terraMA2 application client + * @param {*} client : terraMA2 database client + * @param {*} storage : storage to execute + */ +async function runStorage(clientSocket, client, storage){ + return new Promise(async function resolvePromise(resolve, reject){ + try{ + console.log("Starting ", storage.name, " ", moment().format()); + logger.log("info","Starting: ", storage.name ); + + if (storage.erase_all) //flag has priority + storage.keep_data = 0; + + var params = { + clientSocket: clientSocket, + schema: schema, + storage: storage, + client: client, + logger: logger + } + + await sto_core.createMessageTable(params); + + //Verifies which data_serie type will be stored + logger.debug(storage.name + ": Checking which date will be deleted"); + var select_data_type= "SELECT data_series_semantics.code FROM \ + "+schema+".storages, \ + "+schema+".data_series, \ + "+schema+".data_series_semantics WHERE \ + data_series_semantics.id = data_series.data_series_semantics_id AND \ + data_series.id = \'" + storage.data_series_id + "\'"; + // console.log(select_data_type); + var res1 = await client.query(select_data_type); + + var data_serie_code = res1.rows[0].code; + storage.data_serie_code = res1.rows[0].code; + switch (data_serie_code){ + case "GRID-geotiff": + var res_storage = await sto_core.StoreTIFF(params) + storage.process.last_process_timestamp = moment(); + storage.process.status = StatusLog.DONE; + var msg ; + if (res_storage.flag){ + if (storage.zip){ + msg = storage.name + ": added files: " + res_storage.deleted_files + " from " + res_storage.path + " to " + res_storage.zipfile; + } + else if (storage.backup){ + msg = storage.name + ": moved files: " + res_storage.moved_files + " from " + res_storage.path + " to " + res_storage.data_out; + } + else{ + msg =storage.name + ": removed files: " + res_storage.deleted_files + " from " + res_storage.path; + } + + logger.log('info', msg); + logger.log('info', "Finalized execution of " + storage.name); + if (res_storage.moved_files === 0 && res_storage.deleted_files === 0){ + storage.process.description = "No data to process" + } + else{ + storage.process.data_timestamp = res_storage.data_timestamp; + storage.process.description = msg; + } + } + else{ + storage.process.description = res_storage.description; + logger.log('info', "Finalized execution of " + storage.name + " -> " + res_storage.description); + } + + break; + + case "DCP-single_table": + await sto_core.StoreSingleTable(params); + + break; + case "DCP-postgis": + case "ANALYSIS_MONITORED_OBJECT-postgis": + case "OCCURRENCE-postgis": + await sto_core.StoreNTable(params); + + logger.log('info', "Finalized execution of " + storage.name); + storage.process.last_process_timestamp = moment(); + storage.process.status = StatusLog.DONE; + + break; + + default: + console.log("erro -"); + logger.error(storage.name + ": Invalid Data Serie Type!"); + throw("Invalid Data Serie Type!"); + } + + var obj = { + automatic : true,//storage.automatic_schedule_id ? true : false, + execution_date : moment().format(), + instance_id : service_instance_id, + result : true, + process_id : storage.id, + storage : storage + } + + return resolve(obj); + } + catch(e){ + console.log(e); + return reject(e); + } + }); +}; + +function updateStorage(newstorage) +{ + for(let i = 0; i < Storages.length; i++) { + if (Storages[i].id === newstorage.id){ + return Storages.splice(i,1, newstorage); + } + }; +} + +/** + * adds the storage in the array and schedules the execution in the list of jobs + * @param {*} clientSocket : terraMA2 application client + * @param {*} client : terraMA2 database client + * @param {*} storages_new : new storage to add or update + * @param {*} projects : projects list, only to verifiy if project is active + */ +async function addStorage(clientSocket, client, storages_new, projects){ + try{ + for (var storage of storages_new){ + + var storagefound = Storages.find(s => s.id === storage.id); + + if (storagefound){ + updateStorage(storage); + logger.log("info", "Storage Updated: ", storage.name); + } + else { + Storages.push(storage); + if (storage.active){ + logger.log("info", "Storage Added: ", storage.name); + } + } + if (storage.active){ + var res = await client.query("Select * from " + schema + ".projects where id = \'" + storage.project_id + "\'"); + if (res.rowCount){ + if (res.rows[0].active){ + if (storage.schedule_type.toString() === ScheduleType.MANUAL || !storage.schedule_id){//without schedule, manual, so not cron + joblist.push({ + id: storage.id, + job: undefined + }); + } + else{ + var sql = "Select * from " + schema + ".schedules where id = \'" + storage.schedule_id + "\'"; + console.log(sql); + var res1 = await client.query(sql); + if (res1.rowCount){ + //when it is weekly, it should subtract one from the value, the bank stores from 1 to 7, + //but cron uses from 0 to 6 (Sunday to Saturday) + var freq = res1.rows[0].frequency ? res1.rows[0].frequency : res1.rows[0].schedule - 1; + var unit = res1.rows[0].frequency_unit ? res1.rows[0].frequency_unit : res1.rows[0].schedule_unit; + if (freq === undefined || unit === undefined){//without schedule, manual, so not cron + joblist.push({ + id: storage.id, + job: undefined + }); + } + else{ + var freq_seconds = moment.duration(freq, unit).asSeconds(); + var start_time = res1.rows[0].frequency_start_time ? + new moment(res1.rows[0].frequency_start_time, "HH:mm:ssZ") : res1.rows[0].schedule_time ? + new moment(res1.rows[0].schedule_time, "HH:mm:ssZ") : "0:0:0"; + + var rule; + if (res1.rows[0].schedule){ + switch (res1.rows[0].schedule_unit.toLowerCase()){ + case 'w': + case 'wk': + case 'week': + case 'weeks': + rule = start_time.second() + " " + start_time.minute() + " " + start_time.hour() + " * * " + freq; + break; + } + } + else{ + switch (unit.toLowerCase()){ + case 's': + case 'sec': + case 'second': + case 'seconds': + rule = "*/"+freq + " * * * * *"; + break; + case 'min': + case 'minute': + case 'minutes': + rule = start_time.second() + " */" + freq + " * * * *"; + break; + case 'h': + case 'hour': + case 'hours': + rule = start_time.second() + " " + start_time.minute() + " */" + freq + " * * *"; + break; + case 'd': + case 'day': + case 'days': + rule = start_time.second() + " " + start_time.minute() + " " + start_time.hour() + " */" + freq + " * *"; + break; + } + } + + var newjob = new CronJob(rule, async function(){ + for (var job of joblist) { + if (job.job === this){ + storage = Storages.find(s => s.id === job.id) ; + logger.debug(storage.name + ": Setting schedule " + this.cronTime.source); + console.log(storage.name, job.job.cronTime.source); + this.stop(); + runStorage(clientSocket, client, storage) + .catch(err =>{ + console.log(storage.name, err); + this.start(); + }) + .then(obj => { + var buffer = TcpManager.makebuffer_be(Signals.PROCESS_FINISHED_SIGNAL, obj) ; + clientSocket.write(buffer); + logger.debug(storage.name + ": Finished"); + this.start(); + var lastdate = this.lastDate(); + var nextdates = this.nextDates(); + logger.log("info", storage.name + ": last date " + lastdate + " next date execution " + nextdates.format()); + }); + break; + } + } + }); + + logger.log("info", storage.name + " scheduled to run: " + newjob.nextDates().format()); + + var update = false; + for (var job of joblist){ + if (job.id === storage.id){ + job.job = newjob; + update = true; + break; + } + } + + if (!update) + joblist.push({ + id: storage.id, + job: newjob + }); + + newjob.start(); + } + } + } + } + } + } + } + } + catch(e){ + logger.error(e); + throw e; + } +} + +let beginOfMessage = "(BOM)\0"; +let endOfMessage = "(EOM)\0"; + +let extraData; +let tempBuffer; +let parsed; +let service_instance_id; +let service_instance_name; +let serviceLoaded_ = false; +let isShuttingDown_ = false; +let start_time; + +/** + * Listen on provided port, on all network interfaces. + */ +server.listen(port, 'localhost', () =>{ + console.log('Storage is running on port ' + port +'.'); + logger.log('info', 'Storage is running on port ' + port); +}); + +server.on('error', onError); +server.on('listening', onListening); + +let config_db = { + user: contextConfig.username, + password: contextConfig.password, + host: contextConfig.host, + port: contextConfig.port, + database: contextConfig.database +}; + +console.log('Config_db', config_db); + +/** + * Handling received messages from TerraMA2 + * @param {*} clientSocket : TerraMA2 application client + * @param {*} parsed : message received + * @param {*} client_Terrama2_db : database client + */ +async function messageTreatment(clientSocket, parsed, client_Terrama2_db){ + + switch(parsed.signal){ + case Signals.TERMINATE_SERVICE_SIGNAL: + console.log("TERMINATE_SERVICE_SIGNAL"); + isShuttingDown_ = true; + logger.log('info', 'Storage service finalized'); + var buffer = await TcpManager.makebuffer_be(Signals.TERMINATE_SERVICE_SIGNAL); + clientSocket.write(buffer); + process.exit(0); + + break; + + case Signals.STATUS_SIGNAL: + if (!serviceLoaded_){ + var buffer = await TcpManager.makebuffer_be(Signals.STATUS_SIGNAL, {service_loaded:false}) ; + } + else{ + var obj={ + instance_id : service_instance_id, + instance_name : service_instance_name, + logger_online : true, + service_loaded : true, + shutting_down : false, + start_time : start_time, + terrama2_version : version + } + var buffer = await TcpManager.makebuffer_be(Signals.STATUS_SIGNAL, obj) ; + } + await clientSocket.write(buffer); + break; + + case Signals.ADD_DATA_SIGNAL: + console.log("ADD_DATA_SIGNAL"); + if (parsed.message.Storages){ + try{ + await addStorage(clientSocket, client_Terrama2_db, parsed.message.Storages, parsed.message.Projects); + + var buffer = await TcpManager.makebuffer_be(Signals.VALIDATE_PROCESS_SIGNAL, {}) ; + clientSocket.write(buffer); + } + catch(e){ + logger.error(e); + } + } + + break; + case Signals.START_PROCESS_SIGNAL: + console.log("START_PROCESS_SIGNAL"); + var storage_id = parsed.message.ids[0]; + + for (var job of joblist) { + if (job.id === storage_id){ + var storage = Storages.find(s => s.id === job.id) ; + if (job.job){ + // console.log(storage.name, moment().format(), storage.name, " job ", job.job.cronTime.source, " this ", this.cronTime.source); + job.job.stop(); + } + var obj ={ + instance_id: storage.service_instance_id, + log:{ + data: "", + data_timestamp: "", + last_process_timestamp: moment().format(), + messages: [], + process_id: storage_id, + start_timestamp: moment().format(), + status: 2 + }, + process_id: storage_id + } + var objs =[]; + objs.push(obj); + + var buffer = TcpManager.makebuffer_be(Signals.LOG_SIGNAL, objs) ; + //console.log(buffer.toString()); + clientSocket.write(buffer); + + runStorage(clientSocket, client_Terrama2_db, storage) + .catch(err =>{ + logger.error(err); + if (job.job) job.job.start(); + }) + .then(obj1 => { + var buffer = TcpManager.makebuffer_be(Signals.PROCESS_FINISHED_SIGNAL, obj1) ; + clientSocket.write(buffer); + console.log("Finishing", obj1.storage.name, " ", moment().format()) + if (job.job) job.job.start(); + }); + break; + } + } + break; + + case Signals.LOG_SIGNAL: + //console.log("LOG - process_ids", parsed.message.process_ids); + var begin = parsed.message.begin; + var end = parsed.message.end; + var process_id = parsed.message.process_ids; //lista com ids dos processos + sto_core.getLogs(client_Terrama2_db, schema, service_instance_id, process_id, begin, end) + .catch(err =>{ + logger.error(err); + }) + .then(logs=>{ + var buffer = TcpManager.makebuffer_be(Signals.LOG_SIGNAL, logs) ; + clientSocket.write(buffer); + }); + break; + + case Signals.REMOVE_DATA_SIGNAL: + console.log("REMOVE_DATA_SIGNAL"); + break; + + case Signals.PROCESS_FINISHED_SIGNAL: + console.log("PROCESS_FINISHED_SIGNAL"); + serviceLoaded_ = false; + break; + + case Signals.UPDATE_SERVICE_SIGNAL: + console.log("UPDATE_SERVICE_SIGNAL"); + //Need to discover how many theads are supported + if (parsed.message.instance_id){ + serviceLoaded_ = true; + service_instance_id = parsed.message.instance_id; + service_instance_name = parsed.message.instance_name; + + config_db.user= parsed.message.log_database.PG_USER; + config_db.password= parsed.message.log_database.PG_PASSWORD; + config_db.host= parsed.message.log_database.PG_HOST; + config_db.port= parsed.message.log_database.PG_PORT; + config_db.database= parsed.message.log_database.PG_DB_NAME; + + var obj={ + serviceLoaded_ : true, + instance_id : service_instance_id, + instance_name : service_instance_name + } + } + else{ + var buffer = await TcpManager.makebuffer_be(Signals.UPDATE_SERVICE_SIGNAL, parsed.message) ; + console.log("UPDATE_SERVICE_SIGNAL",); + clientSocket.write(buffer); + } + break; + + case Signals.VALIDATE_PROCESS_SIGNAL: + var buffer = await TcpManager.makebuffer_be(Signals.VALIDATE_PROCESS_SIGNAL, obj) ; + console.log("VALIDATE_PROCESS_SIGNAL"); + clientSocket.write(buffer); + break; + } +} + +let pool = new pg.Pool(config_db); + +pool.connect().then(client_Terrama2_db => { + logger.log('info', 'Conected to database ' + config_db.database); + server.on('connection', async function(clientSocket) { + console.log('CONNECTED: ' + clientSocket.remoteAddress + ':' + clientSocket.remotePort); + logger.log('info','CONNECTED: ' + clientSocket.remoteAddress + ':' + clientSocket.remotePort); + + start_time = moment().format(); + + clientSocket.on('data', async function(byteArray) { + + //console.log("RECEIVED: ", byteArray); + + clientSocket.answered = true; + + // append and check if the complete message has arrived + tempBuffer = storageUtils._createBufferFrom(tempBuffer, byteArray); + + let completeMessage = true; + // process all messages in the buffer or until we get a incomplete message + while(tempBuffer && completeMessage) { + try { + let bom = tempBuffer.toString('utf-8', 0, beginOfMessage.length); + while(tempBuffer.length > beginOfMessage.length && bom !== beginOfMessage) { + // remove any garbage left in the buffer until a valid message + // obs: should never happen + tempBuffer = new Buffer.from(tempBuffer.slice(1)); + bom = tempBuffer.toString('utf-8', 0, beginOfMessage.length); + } + + if(bom !== beginOfMessage) { + // no begin of message header: + // - clear the buffer + // - wait for a new message + parsed = storageUtils.parseByteArray(byteArray); + if (parsed.message){ + tempBuffer = undefined; + completeMessage = false; + } + else{ + tempBuffer = undefined; + throw new Error("Invalid message (BOM)"); + } + } + else{ + const messageSizeReceived = tempBuffer.readUInt32BE(beginOfMessage.length); + const headerSize = beginOfMessage.length + endOfMessage.length; + const expectedLength = messageSizeReceived + 4; + if(tempBuffer.length < expectedLength+headerSize) { + // if we don't have the complete message + // wait for the rest + completeMessage = false; + return; + } + var end_pos = tempBuffer.indexOf(endOfMessage); + const eom1 = tempBuffer.toString('ascii', end_pos, end_pos+endOfMessage.length); + // const eom = tempBuffer.toString('ascii', expectedLength + beginOfMessage.length, expectedLength+headerSize); + if(eom1 !== endOfMessage) { + // we should have a complete message and and end of message mark + // if we arrived here we got an ill-formed message + // clear the buffer and raise an error + tempBuffer = undefined; + throw new Error("Invalid message (EOM)"); + } + + // if we got many messages at once + // hold the buffer we the extra messages for processing + if(tempBuffer.length > expectedLength+headerSize) { + extraData = new Buffer.from(tempBuffer.slice(expectedLength + headerSize)); + } else { + extraData = undefined; + } + + // get only the first message for processing + tempBuffer = new Buffer.from(tempBuffer.slice(beginOfMessage.length, expectedLength+beginOfMessage.length)); + parsed = storageUtils.parseByteArray(tempBuffer); + // get next message in the buffer for processing + tempBuffer = extraData; + } + + //console.log("Size: " + parsed.size + " Signal: " + parsed.signal + " Message: " + JSON.stringify(parsed.message, null, 4)); + + await messageTreatment(clientSocket, parsed, client_Terrama2_db); + } + catch(e){ + logger.error(e); + throw (e); + } + } + }); + clientSocket.on('error', function(err) { + console.log(err); + }); + }); +}); + +// detecting sigint error and then close server +process.on('SIGINT', async () => { + TcpService.finalize(); + TcpService.disconnect(); + + server.close(() => { + logger.log('info','Storage Service finalized'); + + process.exit(0); + }); + + for (var key in connections) + connections[key].destroy(); +}); + +process.on('message', async (msg) =>{ + console.log(msg); +}) + + +/** + * Event listener for HTTP server "error" event. + */ +function onError(error) { + if (error.syscall !== 'listen') { + console.log(error); + } + + var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.log(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.log(bind + ' is already in use'); + process.exit(1); + break; + default: + console.log(error); + } +} + +function handle(signal){ + console.log('Received ${signal}'); +} + +/** + * Event listener for HTTP server "listening" event. + */ +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; + + console.log('Listening on ' + bind); +} + +io.attach(server); +load('sockets').into(io); diff --git a/webapp/storage/utils.js b/webapp/storage/utils.js new file mode 100644 index 000000000..112d1f878 --- /dev/null +++ b/webapp/storage/utils.js @@ -0,0 +1,256 @@ +"use strict"; + +const pg = require('pg'); +var Signals = require('./../core/Signals'); +const Regex = require('xregexp'); + +function getTcpSignal (value) { + switch(value) { + case Signals.TERMINATE_SERVICE_SIGNAL: + case Signals.STATUS_SIGNAL: + case Signals.ADD_DATA_SIGNAL: + case Signals.START_PROCESS_SIGNAL: + case Signals.LOG_SIGNAL: + case Signals.REMOVE_DATA_SIGNAL: + case Signals.PROCESS_FINISHED_SIGNAL: + case Signals.UPDATE_SERVICE_SIGNAL: + case Signals.VALIDATE_PROCESS_SIGNAL: + return value; + default: + return -1; + } +}; + +var Utils = module.exports = { + clone: function(object) { + return cloneDeep(object); + }, + +/** + * Creates a new Buffer based on any number of Buffer + * + * @private + * @param {Buffer} arguments Any number of Buffer as arguments, may have undefined itens. + * @return {Buffer} The new Buffer created out of the list. + */ +_createBufferFrom : function () { + let size = 0; + for (var i = 0; i < arguments.length; i++) { + if(arguments[i]) { + size += arguments[i].length; + } + } + + let tmp = new Buffer(size); + let offset = 0; + for (var i = 0; i < arguments.length; i++) { + if(arguments[i]) { + tmp.set(new Buffer.from(arguments[i]), offset); + offset+=arguments[i].length; + } + } + + return tmp; + }, + + /** + This method parses the bytearray received. + @param {Buffer} byteArray - a nodejs buffer with bytearray received + @return {Object} object - a javascript object with signal, message and size + + */ + parseByteArray : function (byteArray) { + var messageSizeReceived = byteArray.readUInt32BE(0); + var signalReceived = byteArray.readUInt32BE(4); + var len = byteArray.length; + var rawData = byteArray.slice(8, byteArray.length); + // console.log(rawData.toString()); + + // validate signal + var signal = getTcpSignal(signalReceived); + if (signal == -1){ + return { + size: 0, + signal: Signals.STATUS_SIGNAL, + message: {} + }; + } + + var jsonMessage; + + try{ + if (rawData.length === 0) { + jsonMessage = {}; + } else { + jsonMessage = JSON.parse(rawData); + } + } + catch(e){ + console.log(e); + console.log("rawData:", rawData.toString()); + jsonMessage = {}; + } + + return { + size: messageSizeReceived, + signal: signal, + message: jsonMessage + }; + }, + + makebuffer : async function (signal, object) { + try { + if(isNaN(signal)) { throw TypeError(signal + " is not a valid signal!"); } + + var totalSize; + var jsonMessage = ""; + + if (object) { + jsonMessage = JSON.stringify(object).replace(/\":/g, "\": "); + + // The size of the message plus the size of two integers, 4 bytes each + totalSize = jsonMessage.length + 4; + } else { totalSize = 4; } + + // creating buffer to store message + var bufferMessage = new Buffer(jsonMessage); + + // Creates the buffer to be sent + var buffer = Buffer.alloc(bufferMessage.length + 8 + 12); + + if (object) { + // Writes the message (string) in the buffer with UTF-8 encoding + bufferMessage.copy(buffer, 14, 0, bufferMessage.length); + } + + // checking bufferMessage length. If it is bigger than jsonMessage, + // then there are special chars and the message size must be adjusted. + if (bufferMessage.length > jsonMessage.length) { totalSize = bufferMessage.length + 4; } + + // Writes the buffer size (unsigned 32-bit integer) in the buffer with big endian format + buffer.write(beginOfMessage, 0); + buffer.writeUInt32BE(totalSize, 6); + + // // Writes the signal (unsigned 32-bit integer) in the buffer with big endian format + buffer.writeUInt32BE(signal, 10); + buffer.write(endOfMessage, buffer.length - 6); + + console.log("makebuffer", buffer); + return buffer; + } catch (e) { + throw e;// reject(e); + } + }, + + + databaseConnect : async function (database, logger){ + try{ + const config = { + user: database.PG_USER, + password: database.PG_PASSWORD, + host: database.PG_HOST, + port: database.PG_PORT, + database: database.PG_DB_NAME, + ssl: true + }; + + logger.info("Initializing database " + database.PG_DB_NAME); + var pool = new pg.Pool(config); + pool.connect().then(client =>{ + return Promise.resolve(client); + }); + } + catch (e){ + console.log(e); + logger.error(e); + return Promise.reject(e); + } + }, + + terramaMask2Regex : async function (mask){ + var regex = new Regex('\\%\\(\\)\\%'); + var list = mask.split(regex); + var listout = ''; + + var m; + for(var i=0; i< list.length;i++){ + m = list[i]; + if(mask.startsWith("%(")){ + if (i%2==0){ + m = "("+m+")"; + continue; + } + } + else + if (i%2!=0){ + m = "("+m+")"; + continue; + } + + // escape regex metacharacters + m = m.replace("+", "\\+"); + m = m.replace("(", "\\("); + m = m.replace(")", "\\)"); + m = m.replace("[", "\\["); + m = m.replace("]", "\\]"); + m = m.replace("{", "\\{"); + m = m.replace("}", "\\}"); + m = m.replace("^", "\\^"); + m = m.replace("$", "\\$"); + m = m.replace("&", "\\&"); + m = m.replace("|", "\\|"); + m = m.replace("?", "\\?"); + m = m.replace(".", "\\."); + + /* + * + YYYY year with 4 digits [0-9]{4} + YY year with 2 digits [0-9]{2} + MM month with 2 digits 0[1-9]|1[012] + DD day with 2 digits 0[1-9]|[12][0-9]|3[01] + hh hout with 2 digits [0-1][0-9]|2[0-4] + mm minutes with 2 digits [0-5][0-9] + ss seconds with 2 digits [0-5][0-9] + * any character, any times .* + */ + + m = m.replace(/%YYYY/gi, "(?[0-9]{4})"); + m = m.replace(/%YY/gi, "(?[0-9]{2})"); + m = m.replace("%MM", "(?0[1-9]|1[012])"); + m = m.replace("%DD", "(?0[1-9]|[12][0-9]|3[01])"); + m = m.replace("%JJJ", "(?\\d{3})"); + m = m.replace("%hh", "(?[0-1][0-9]|2[0-4])"); + m = m.replace("%mm", "(?[0-5][0-9])"); + m = m.replace("%ss", "(?[0-5][0-9])"); + m = m.replace("*", ".*"); + // add a extension validation in case of the name has it + m = m + "(?(\\.(gz|zip|rar|7z|tar))+)?$"; + listout = listout + m; + } + + //console.log("no terrama2 " + listout); + return listout; + }, + + /** + * Normalize a port into a number, string, or false. + */ + normalizePort : function (val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; + } + +} + + \ No newline at end of file diff --git a/webapp/views/administration/service.html b/webapp/views/administration/service.html index c2d350548..329dc92f4 100644 --- a/webapp/views/administration/service.html +++ b/webapp/views/administration/service.html @@ -163,6 +163,7 @@

+ diff --git a/webapp/views/administration/storage.html b/webapp/views/administration/storage.html new file mode 100644 index 000000000..b30803c7f --- /dev/null +++ b/webapp/views/administration/storage.html @@ -0,0 +1,31 @@ +{% extends "../base/layout.html" %} + +{% set tabActive = "storage" %} + +{% block title %} TerraMA² {{ i18n.__("Storage Registration") }} {% endblock %} + +{% block javascripts %} + + + + + +{% endblock %} + + +{% block styles %} + + + +{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/webapp/views/administration/storages.html b/webapp/views/administration/storages.html new file mode 100644 index 000000000..ccfa34941 --- /dev/null +++ b/webapp/views/administration/storages.html @@ -0,0 +1,39 @@ +{% extends "../base/layout.html" %} + +{% set tabActive = "storage" %} + +{% block title %} TerraMA² {{ i18n.__('Storage') }} {% endblock %} + +{% block javascripts %} + + + +{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/webapp/views/base/layout.html b/webapp/views/base/layout.html index 4a6e3b0f1..86eea79b0 100644 --- a/webapp/views/base/layout.html +++ b/webapp/views/base/layout.html @@ -1,7 +1,7 @@ {% set MENU_URLS = {} %} {# set MENU_URLS = {"projects": {name: 'Projects', url: "configuration/projects", icon: "fa-sitemap"}} #} -{% set MENU_URLS["data-provider"] = {name: 'Data Servers', url: "configuration/providers", icon: "fa-database"} %} +{% set MENU_URLS["data-provider"] = {name: 'Data Servers', url: "configuration/providers", icon: "fa-server"} %} {% set MENU_URLS["dynamic-data"] = {name: 'Dynamic Data', url: "configuration/dynamic/dataseries", icon: "fa-clock-o"} %} {% set MENU_URLS["static-data"] = {name: 'Static Data', url: "configuration/static/dataseries", icon: "fa-folder"} %} {% set MENU_URLS["analysis"] = {name: 'Analysis', url: "configuration/analysis", icon: "fa-search"} %} @@ -190,7 +190,7 @@
    + style="display: {% if (tabActive === 'services' || tabActive === 'users' || tabActive === 'storage') %} block; {% else %} none;{% endif %}">
  • @@ -201,6 +201,12 @@
  • + +
  • + + + +
{% endif %} @@ -271,7 +277,7 @@ event.preventDefault(); }); - var collapsed = { [collapsed]}; + var collapsed = {[collapsed]}; $("#collapseButton").click(function (event) { var self = this; diff --git a/webcomponents/package-lock.json b/webcomponents/package-lock.json index c5488eb87..525629787 100644 --- a/webcomponents/package-lock.json +++ b/webcomponents/package-lock.json @@ -1134,9 +1134,9 @@ "dev": true }, "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esutils": { @@ -1258,9 +1258,9 @@ "dev": true }, "grunt": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.2.tgz", - "integrity": "sha1-TmpeaVtwRy/VME9fqeNCNoNqc7w=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.4.tgz", + "integrity": "sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==", "dev": true, "requires": { "coffeescript": "~1.10.0", @@ -1271,14 +1271,15 @@ "glob": "~7.0.0", "grunt-cli": "~1.2.0", "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~1.0.0", - "grunt-legacy-util": "~1.0.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", "iconv-lite": "~0.4.13", - "js-yaml": "~3.5.2", + "js-yaml": "~3.13.0", "minimatch": "~3.0.2", + "mkdirp": "~0.5.1", "nopt": "~3.0.6", "path-is-absolute": "~1.0.0", - "rimraf": "~2.2.8" + "rimraf": "~2.6.2" }, "dependencies": { "grunt-cli": { @@ -1352,68 +1353,87 @@ } }, "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", "dev": true }, "grunt-legacy-log": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.1.tgz", - "integrity": "sha512-rwuyqNKlI0IPz0DvxzJjcEiQEBaBNVeb1LFoZKxSmHLETFUwhwUrqOsPIxURTKSwNZHZ4ht1YLBYmVU0YZAzHQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", "dev": true, "requires": { "colors": "~1.1.2", - "grunt-legacy-log-utils": "~1.0.0", + "grunt-legacy-log-utils": "~2.0.0", "hooker": "~0.2.3", - "lodash": "~4.17.5", - "underscore.string": "~3.3.4" + "lodash": "~4.17.5" } }, "grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", "dev": true, "requires": { - "chalk": "~1.1.1", - "lodash": "~4.3.0" + "chalk": "~2.4.1", + "lodash": "~4.17.10" }, "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", "dev": true, "requires": { "async": "~1.5.2", "exit": "~0.1.1", "getobject": "~0.1.0", "hooker": "~0.2.3", - "lodash": "~4.3.0", - "underscore.string": "~3.2.3", - "which": "~1.2.1" + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" }, "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true - }, - "underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", - "dev": true + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -1455,10 +1475,13 @@ "dev": true }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "indent-string": { "version": "2.1.0", @@ -1549,13 +1572,13 @@ "dev": true }, "js-yaml": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", - "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "^1.0.2", - "esprima": "^2.6.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "js2xmlparser": { @@ -1622,9 +1645,9 @@ } }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "loose-envify": { @@ -2020,10 +2043,29 @@ "dev": true }, "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } }, "safe-buffer": { "version": "5.1.1", @@ -2031,6 +2073,12 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -2085,9 +2133,9 @@ "dev": true }, "sprintf-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", - "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "dev": true }, "string_decoder": { @@ -2216,9 +2264,9 @@ } }, "underscore.string": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", - "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", "dev": true, "requires": { "sprintf-js": "^1.0.3", diff --git a/webcomponents/package.json b/webcomponents/package.json index 8ec0f3e63..dde64cc36 100644 --- a/webcomponents/package.json +++ b/webcomponents/package.json @@ -6,7 +6,10 @@ "grunt": "grunt" }, "devDependencies": { - "grunt": "^1.0.2", + "@babel/core": "^7.0.0-beta.49", + "@babel/preset-env": "^7.0.0-beta.49", + "grunt": "^1.0.4", + "grunt-babel": "^7.0.0", "grunt-banner": "^0.6.0", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-cssmin": "^1.0.1", @@ -14,9 +17,6 @@ "grunt-jsdoc": "^2.1.0", "jsdoc": "^3.4.0", "requirejs": "^2.2.0", - "@babel/core": "^7.0.0-beta.49", - "@babel/preset-env": "^7.0.0-beta.49", - "grunt-babel": "^7.0.0", "uglify-es": "^3.3.9" } }