diff --git a/public-interface/admin/createDB.js b/public-interface/admin/createDB.js index 74c594f1..7e29f174 100644 --- a/public-interface/admin/createDB.js +++ b/public-interface/admin/createDB.js @@ -28,35 +28,74 @@ CreateDB.prototype.create = function(){ host: config.postgres.options.replication.write.host, database: 'postgres', password: config.postgres.su_password, - port: config.postgres.options.replication.write.port, + port: config.postgres.options.port, }); const query = {text: 'CREATE DATABASE ' + config.postgres.database + ';' }; - client.connect() + client.connect( + ) + .then(() => console.log("Connected")) + .catch(function(err) { + console.log("Cannot connect: " + err); + process.exit(1); + }) + .then(function() { + console.log("Executing query: ", query.text); + return client.query(query); + }) + /*.catch(function(err) { + console.log("Cannot create db: " + err); + console.log("OK to continue!"); + })*/ .then(function() { - console.log("Connected to postgres"); + query.text = 'CREATE USER ' + config.postgres.su_username + + ' WITH PASSWORD \'' + config.postgres.su_password + '\';'; + console.log("Trying to create PG user. Executing query: ", query.text); return client.query(query); }) + .catch(function(err) { + console.log("Cannot create user: " + err); + console.log("OK to continue "); + }) .then(function() { - query.text = 'CREATE USER ' + config.postgres.username + - ' WITH PASSWORD ' + config.postgres.password + ';' + - ' GRANT CONNECT ON DATABASE ' + config.postgres.database + + query.text = ' GRANT CONNECT ON DATABASE ' + config.postgres.database + ' TO ' + config.postgres.username + ';'; - return client.query(); + console.log("Trying grant rights to PG user. Executing query: ", query.text); + return client.query(query); + }) + .catch(function(err) { + console.log("Cannot create user: " + err); + console.log("OK to continue "); }) .then(function() { - query.text = 'CREATE DATABASE test; ' + - 'GRANT ALL PRIVILEGES ON DATABASE test TO ' + + query.text = 'CREATE DATABASE test;'; + console.log("Trying to create test database. Executing query: ", query.text); + return client.query(query); + }) + .then(function() { + query.text = 'GRANT ALL PRIVILEGES ON DATABASE test TO ' + config.postgres.su_username + '; ' + 'GRANT CONNECT ON DATABASE test TO ' + config.postgres.username + ';'; - return client.query(); + console.log("Trying to create test database. Executing query: ", query.text); + return client.query(query); + }) + .catch(function(err) { + console.log("Cannot create db test: " + err); + console.log("OK to continue "); }) .then(() => { + console.log("Trying to create DB models ..."); return models.createDatabase(); + + }) + .catch(function(err) { + console.log("Cannot create models: " + err); + console.log("OK to continue "); }) .then(function() { + console.log("Trying to create system users."); return systemUsers.create(); }) .then(function() { diff --git a/public-interface/admin/index.js b/public-interface/admin/index.js index 32f1f2d0..fd42c66c 100755 --- a/public-interface/admin/index.js +++ b/public-interface/admin/index.js @@ -23,7 +23,9 @@ var addUser = require('./addUser'); var RemoveTestUser = require('./removeTestUser'); var ResetDB = require('./resetDB'); var CreateDB = require('./createDB'); +var UpdateDB = require('./updateDB'); var command = process.argv[2]; +var arg = process.argv[3]; switch (command) { case 'addUser': @@ -44,6 +46,18 @@ case 'createDB': var databaseCreater = new CreateDB(); databaseCreater.create(); break; +case 'updateDB': + var databaseUpdater = new UpdateDB(); + databaseUpdater.update(arg); + break; +case 'revertDB': + var databaseUpdater = new UpdateDB(); + databaseUpdater.revertOne(arg); + break; +case 'revertAllDB': + var databaseUpdater = new UpdateDB(); + databaseUpdater.revertAll(arg); + break; default: console.log ("Command : ", command , " not supported "); } diff --git a/public-interface/admin/updateDB.js b/public-interface/admin/updateDB.js new file mode 100644 index 00000000..7a28027e --- /dev/null +++ b/public-interface/admin/updateDB.js @@ -0,0 +1,77 @@ +const config = require('../config'); +const { exec } = require('child_process'); + +var UpdateDB = function(){}; +var cdCommand = "cd /app/iot-entities/postgresql/migrations;"; + + +UpdateDB.prototype.update = function(test) { + var database = config.postgres.database; + if (test === "test") { + database = "test"; + } + + var dbupdateCommand = "npx sequelize-cli db:migrate --url postgres://" + + config.postgres.su_username + + ":" + config.postgres.su_password + + "@" + config.postgres.options.replication.write.host + + "/" + database; + console.log("Executing " + cdCommand + dbupdateCommand); + exec(cdCommand + dbupdateCommand, (err, stdout, stderr) => { + if (err) { + console.log("Error: ", err); + process.exit(1); + } + console.log(`stdout: ${stdout}`); + console.log(`stderr: ${stderr}`); + process.exit(0); + }); +}; + +UpdateDB.prototype.revertOne = function(test) { + var database = config.postgres.database; + if (test === "test") { + database = "test"; + } + + var dbupdateCommand = "npx sequelize-cli db:migrate:undo --url postgres://" + + config.postgres.su_username + + ":" + config.postgres.su_password + + "@" + config.postgres.options.replication.write.host + + "/" + database; + console.log("Executing " + cdCommand + dbupdateCommand); + exec(cdCommand + dbupdateCommand, (err, stdout, stderr) => { + if (err) { + console.log("Error: ", err); + process.exit(1); + } + console.log(`stdout: ${stdout}`); + console.log(`stderr: ${stderr}`); + process.exit(0); + }); +}; + +UpdateDB.prototype.revertAll = function(test) { + var database = config.postgres.database; + if (test === "test") { + database = "test"; + } + + var dbupdateCommand = "npx sequelize-cli db:migrate:undo:all --url postgres://" + + config.postgres.su_username + + ":" + config.postgres.su_password + + "@" + config.postgres.options.replication.write.host + + "/" + database; + console.log("Executing " + cdCommand + dbupdateCommand); + exec(cdCommand + dbupdateCommand, (err, stdout, stderr) => { + if (err) { + console.log("Error: ", err); + process.exit(1); + } + console.log(`stdout: ${stdout}`); + console.log(`stderr: ${stderr}`); + process.exit(0); + }); +}; + +module.exports = UpdateDB; diff --git a/public-interface/dashboard/public/js/services/alerts_service.js b/public-interface/dashboard/public/js/services/alerts_service.js index a565a59e..bc7a2fc9 100644 --- a/public-interface/dashboard/public/js/services/alerts_service.js +++ b/public-interface/dashboard/public/js/services/alerts_service.js @@ -40,7 +40,8 @@ iotServices.factory('alertsService', ['$http', 'utilityService','sessionService' method: 'GET', url: url, params: { - "_" : utilityService.timeStamp() + "_" : utilityService.timeStamp(), + "active": true } }).success(function(data){ summary.unread = data; @@ -56,7 +57,8 @@ iotServices.factory('alertsService', ['$http', 'utilityService','sessionService' method: 'GET', url: url, params: { - "_" : utilityService.timeStamp() + "_" : utilityService.timeStamp(), + "active": true } }).success(function(data) { summary.unread = removeReadAlerts(data); @@ -71,7 +73,8 @@ iotServices.factory('alertsService', ['$http', 'utilityService','sessionService' method: 'GET', url: url, params: { - "_" : utilityService.timeStamp() + "_" : utilityService.timeStamp(), + "active": true } }).success(successCallback).error(errorCallback); }); @@ -87,7 +90,10 @@ iotServices.factory('alertsService', ['$http', 'utilityService','sessionService' .then(function(url){ var requestOptions = { method: 'PUT', - url: url + url: url, + params:{ + "active": true + } }; $http(requestOptions).success(function(data, status){ fireStatusUpdatedEvent(ngScope, {alert: alert, newStatus: newStatus}); diff --git a/public-interface/doc/api/alerts.raml b/public-interface/doc/api/alerts.raml index 82ace252..c80304c2 100644 --- a/public-interface/doc/api/alerts.raml +++ b/public-interface/doc/api/alerts.raml @@ -64,6 +64,12 @@ get: example: 75 get: is: [ authorization-header, response-errors-alerts, response-errors-generic ] + queryParameters: + active: + description: If true, ignore suppressed events + type: boolean + required: false + example: true description: | **Get Alert information** diff --git a/public-interface/engine/api/v1/alerts.js b/public-interface/engine/api/v1/alerts.js index 4cf78a3c..345ec609 100755 --- a/public-interface/engine/api/v1/alerts.js +++ b/public-interface/engine/api/v1/alerts.js @@ -142,12 +142,37 @@ function parseAlertResponse(data) { return results; } -var findDeviceForAlert = function(alert, callback) { - Device.findByComponentId(alert.conditions[0].components[0].componentId, callback); +var findDeviceForAlert = function(alert) { + return new Promise(function(resolve, reject) { + + var callback = function(err, deviceComponent) { + if (err) { + reject(err); + } else { + resolve(deviceComponent); + } + }; + Device.findByComponentId(alert.conditions[0].components[0].componentId, callback); + }); }; +var checkResetted = function(account, alert, rule) { + return new Promise(function(resolve, reject) { + var callback = function(err, foundAlert) { + if (err) { + reject(err); + } else { + resolve(foundAlert); + } + }; + if (rule.resetType === Alert.resetType.automatic) { + resolve(null); + } else { + Alert.searchNewAlertsWithExternalId(account, rule.id, callback); + } + }); +}; exports.trigger = function (alertData, accountId, hostUrl, resultCallback) { - async.parallel(alertData.map(function (alert) { return function (done) { @@ -158,46 +183,71 @@ exports.trigger = function (alertData, accountId, hostUrl, resultCallback) { }; apiRules.getRule(options, function (err, rule) { if (!err && rule) { - findDeviceForAlert(alert, function(err, deviceComponent) { - if (!err) { - //to internal from rule - var internalAlert = toInternalAlert(accountId, alert, rule, deviceComponent); - internalAlert["externalId"] = rule.externalId; - Alert.new(internalAlert, function(err){ - if (err) { - logger.error('alerts. trigger, error: ' + JSON.stringify(err)); - alert.err = errBuilder.build(errBuilder.Errors.Alert.SavingErrorAA).asResponse(); - } else { - if(hostUrl.indexOf('internal-') > -1) { - internalAlert.host = hostUrl.substr(hostUrl.indexOf('-')+1); - } - else { - internalAlert.host = hostUrl; - } + // sometimes the rule-engine takes some time to adapt to new Rules + // The check for rule status makes sure that a rule which just has been disabled is no + // longer triggering anything + if (rule == null || rule.status !== "Active") { + alert.err = errBuilder.build(errBuilder.Errors.Alert.RuleNotActive).asResponse(); + logger.error('alerts. trigger, error: ' + JSON.stringify(alert)); + done(null, alert); + } else { + var suppressAlert = false; + checkResetted(accountId, alert, rule) + .then((found) => new Promise(function(resolve){ + if (found) { + logger.info("Active alert found with same ruleid. Creating silent alert instead."); + suppressAlert = true; + } + resolve(); + })) + .then(() => findDeviceForAlert(alert)) + .then((deviceComponent) => new Promise(function(resolve, reject){ + //to internal from rule - internalAlert.externalRule = rule; - actuationAlerts.addCommandsToActuationActions(accountId, rule) - .then(function onSuccess() { - actuationAlerts.saveAlertActuations(rule.actions, function (err) { - if (err) { - logger.error('alerts.saveActuations - unable to add new actuation message into DB: ' + JSON.stringify(err)); - } - }); - process.emit("incoming_alert", {alert: internalAlert, rule: rule}); - }, function onError(err) { - logger.error('alerts.getCommands, error: ' + JSON.stringify(err)); - }); + var internalAlert = toInternalAlert(accountId, alert, rule, deviceComponent); + internalAlert["externalId"] = rule.externalId; + if (suppressAlert) { + internalAlert["suppressed"] = suppressAlert; } - done(null, alert); - }); - } else { - logger.error('alerts. trigger, error: ' + JSON.stringify(err)); - alert.err = errBuilder.build(errBuilder.Errors.Alert.SavingErrorAA).asResponse(); - } - }); + Alert.new(internalAlert, function(err){ + if (err) { + logger.error('alerts. trigger, error: ' + JSON.stringify(err)); + alert.err = errBuilder.build(errBuilder.Errors.Alert.SavingErrorAA).asResponse(); + reject(alert); + } else { + if (!suppressAlert) { + if(hostUrl.indexOf('internal-') > -1) { + internalAlert.host = hostUrl.substr(hostUrl.indexOf('-')+1); + } + else { + internalAlert.host = hostUrl; + } - } - else { + internalAlert.externalRule = rule; + actuationAlerts.addCommandsToActuationActions(accountId, rule) + .then(function onSuccess() { + actuationAlerts.saveAlertActuations(rule.actions, function (err) { + if (err) { + logger.error('alerts.saveActuations - unable to add new actuation message into DB: ' + JSON.stringify(err)); + } + }); + process.emit("incoming_alert", {alert: internalAlert, rule: rule}); + }, function onError(err) { + logger.error('alerts.getCommands, error: ' + JSON.stringify(err)); + }); + } + resolve(alert); + } + }); + })) + .then((alert) => {done(null, alert);}) + .catch((err) => { + logger.error('alerts. trigger, error: ' + JSON.stringify(err)); + alert.err = errBuilder.build(errBuilder.Errors.Alert.SavingErrorAA).asResponse(); + done(err, null); + }); + } + } else { alert.err = errBuilder.build(errBuilder.Errors.Alert.RuleNotFound).asResponse(); logger.error('alerts. trigger, error: ' + JSON.stringify(alert)); done(null, alert); @@ -215,14 +265,14 @@ exports.trigger = function (alertData, accountId, hostUrl, resultCallback) { exports.getUnreadAlerts = function (params, resultCallback) { - Alert.findByStatus(params.accountId, params.status, function (err, result) { + Alert.findByStatus(params.accountId, params.status, params.active, function (err, result) { resultCallback(err, result); }); }; exports.getAlerts = function (params, resultCallback) { - Alert.findByStatus(params.accountId, params.status, function (err, result) { + Alert.findByStatus(params.accountId, params.status, params.active, function (err, result) { resultCallback(err, result); }); }; diff --git a/public-interface/engine/handlers/v1/alerts.js b/public-interface/engine/handlers/v1/alerts.js index 6c61fe97..640d0390 100755 --- a/public-interface/engine/handlers/v1/alerts.js +++ b/public-interface/engine/handlers/v1/alerts.js @@ -108,6 +108,9 @@ exports.getAlerts = function(req, res, next) { params.status = req.query.status.split(","); } + if (req.query.active) { + params.active = req.query.active; + } alert.getAlerts(params, function(err, result){ if(!err && result){ res.status(httpStatuses.OK.code).send(result); diff --git a/public-interface/engine/res/errors.js b/public-interface/engine/res/errors.js index 4ebeffe2..7d543eed 100644 --- a/public-interface/engine/res/errors.js +++ b/public-interface/engine/res/errors.js @@ -192,6 +192,7 @@ module.exports = { DeviceNotFound: {code: 8403, status: 400, message: "Device associated to this alert was not found"}, NotFound: {code: 8404, status: 404, message: "Alert not found"}, WrongAlertStatus: {code:8405, status: 400, message: "Wrong alert status"}, + RuleNotActive: {code:8406, status: 400, message: "Rule is not active."}, AlreadyExists: {code: 8409, status: 409, message: "Alert already Exists"}, //Duplicate Alert SavingErrorAA: {code: 8500, status: 500, message: "Error saving Alert"}, //Error saving in Advanced Analytics Backend SavingError: {code: 8501, status: 500, message: "Error saving Alert"}, //Error saving locally diff --git a/public-interface/iot-entities/postgresql/alerts.js b/public-interface/iot-entities/postgresql/alerts.js index f594466d..cdd27c7b 100644 --- a/public-interface/iot-entities/postgresql/alerts.js +++ b/public-interface/iot-entities/postgresql/alerts.js @@ -53,7 +53,7 @@ exports.new = function (newAlert, callback) { }; -exports.findByStatus = function (accountId, status, callback) { +exports.findByStatus = function (accountId, status, isActive, callback) { var filter = { where: { accountId: accountId @@ -64,6 +64,9 @@ exports.findByStatus = function (accountId, status, callback) { if (status) { filter.where["status"] = status; } + if (isActive) { + filter.where["suppressed"] = false; + } return alerts.findAll(filter) .then(function (alert) { @@ -74,6 +77,24 @@ exports.findByStatus = function (accountId, status, callback) { }); }; +exports.searchNewAlertsWithExternalId = function (accountId, externalId, callback) { + var filter = { + where: { + accountId: accountId, + status: statuses.new, + externalId: externalId, + suppressed: false + }, + }; + return alerts.findOne(filter) + .then(function (alert) { + callback(null, alert); + }) + .catch(function (err) { + callback(err, null); + }); +}; + exports.deleteAlerts = function (accountId, status, callback) { var filter = { where: { diff --git a/public-interface/iot-entities/postgresql/helpers/modelsHelper.js b/public-interface/iot-entities/postgresql/helpers/modelsHelper.js index e38c3162..b12ed940 100644 --- a/public-interface/iot-entities/postgresql/helpers/modelsHelper.js +++ b/public-interface/iot-entities/postgresql/helpers/modelsHelper.js @@ -28,7 +28,7 @@ var Accounts = require('./../models/accounts'), DeviceAttributes = require('./../models/deviceAttributes'), DeviceComponents = require('./../models/deviceComponents'), UserInteractionTokens = require('./../models/userInteractionTokens'), - Alerts = require('./../models/alerts'), + Alerts = require('./../migrations/models/alerts'), Actuations = require('./../models/actuations'), AlertComments = require('./../models/alertComments'), ConnectionBindings = require('./../models/connectionBindings'), @@ -96,7 +96,7 @@ module.exports.fillModels = function (sequelize, DataTypes) { allowNull: false } }); - + settings.belongsTo(accounts, { onDelete: 'CASCADE', foreignKey: { diff --git a/public-interface/iot-entities/postgresql/migrations/migrations/20191025111225-Add-Suppressed-Field-To-Alert.js b/public-interface/iot-entities/postgresql/migrations/migrations/20191025111225-Add-Suppressed-Field-To-Alert.js new file mode 100644 index 00000000..1a5ad7fa --- /dev/null +++ b/public-interface/iot-entities/postgresql/migrations/migrations/20191025111225-Add-Suppressed-Field-To-Alert.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn( + { + schema: 'dashboard', + tableName: 'alerts' + }, + 'suppressed', + { + type: Sequelize.BOOLEAN, + defaultValue: false + } + ); + }, + down: (queryInterface) => { + return queryInterface.removeColumn( + { + schema: 'dashboard', + tableName: 'alerts' + }, + 'suppressed' + ); + } +}; diff --git a/public-interface/iot-entities/postgresql/migrations/models/alerts.js b/public-interface/iot-entities/postgresql/migrations/models/alerts.js new file mode 100644 index 00000000..31ce3f89 --- /dev/null +++ b/public-interface/iot-entities/postgresql/migrations/models/alerts.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2014 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +module.exports = function (sequelize, DataTypes) { + return sequelize.define('alerts', { + id: { + type: DataTypes.UUID, + primaryKey: true, + allowNull: false, + defaultValue: DataTypes.UUIDV4 + }, + accountId: { + type: DataTypes.UUID, + allowNull: false + }, + externalId: { + type: DataTypes.STRING(40), + allowNull: false + }, + deviceUID: { + type: DataTypes.UUID, + allowNull: false + }, + reset: { + type: DataTypes.DATE, + allowNull: true + }, + triggered: { + type: DataTypes.DATE, + allowNull: true + }, + dashboardAlertReceivedOn: { + type: DataTypes.DATE, + allowNull: true + }, + dashboardObservationReceivedOn: { + type: DataTypes.DATE, + allowNull: true + }, + status: { + type: DataTypes.ENUM('New', 'Open', 'closed'), + allowNull: false + }, + ruleName: { + type: DataTypes.STRING(255) + }, + priority: { + type: DataTypes.ENUM('High', 'Low', 'Medium') + }, + naturalLangAlert: { + type: DataTypes.TEXT + }, + conditions: { + type: DataTypes.JSON + }, + resetType: { + type: DataTypes.ENUM('Automatic', 'Manual') + } + }, + { + createdAt: 'created', + updatedAt: 'updated', + indexes: [ + { + name: 'alerts_accountId_index', + method: 'BTREE', + fields: ['accountId'] + }, + { + name: 'alerts_ruleExternalId_index', + method: 'BTREE', + fields: ['externalId'] + }, + { + name: 'alerts_deviceUID_index', + method: 'BTREE', + fields: ['deviceUID'] + }, + { + name: 'alerts_status_index', + method: 'BTREE', + fields: ['status'] + } + ], + schema: 'dashboard' + }); +}; diff --git a/public-interface/iot-entities/postgresql/models/alerts.js b/public-interface/iot-entities/postgresql/models/alerts.js index 31ce3f89..5a46969d 100644 --- a/public-interface/iot-entities/postgresql/models/alerts.js +++ b/public-interface/iot-entities/postgresql/models/alerts.js @@ -70,6 +70,10 @@ module.exports = function (sequelize, DataTypes) { }, resetType: { type: DataTypes.ENUM('Automatic', 'Manual') + }, + suppressed: { + type: DataTypes.BOOLEAN, + defaultValue: false } }, { diff --git a/public-interface/package.json b/public-interface/package.json index 1845f878..09cc3e1a 100644 --- a/public-interface/package.json +++ b/public-interface/package.json @@ -22,8 +22,8 @@ "dependencies": { "async": "*", "body-parser": "^1.13.1", - "cbor": "^4.1.5", "borc": "^2.1.0", + "cbor": "^4.1.5", "compression": "^1.5.0", "continuation-local-storage": "^3.2.1", "cookie-parser": "^1.4.4", @@ -56,7 +56,8 @@ "redis": "^2.8.0", "request": "^2.88.0", "request-promise": "^4.2.4", - "sequelize": "^4.41.0", + "sequelize": "^4.44.3", + "sequelize-cli": "^5.5.1", "serve-favicon": "^2.3.0", "shimmer": "^1.2.0", "socket.io": "*",