diff --git a/admin/index.html b/admin/index.html index 4f6cf63..44a32fe 100644 --- a/admin/index.html +++ b/admin/index.html @@ -47,11 +47,11 @@ color: lightgrey; } - .monitoring-state-online { + .node-online, .monitoring-state-online { color: green; } - .monitoring-state-offline { + .node-offline, .monitoring-state-offline { color: red; } diff --git a/admin/js/main.js b/admin/js/main.js index 9ca19d5..846ceed 100644 --- a/admin/js/main.js +++ b/admin/js/main.js @@ -15,6 +15,10 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest return { params: params }; }); + function nullable(value) { + return value ? value : 'N/A'; + } + function formatMoment(unix) { return unix ? moment.unix(unix).fromNow() : 'N/A'; } @@ -41,9 +45,20 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest if (!node) { return; } - return; + + switch (node.values.onlineState) { + case 'ONLINE': + return 'node-online'; + + case 'OFFLINE': + return 'node-offline'; + + default: + return; + } } + var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token')); nodes .listView() @@ -70,6 +85,7 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest ? '' : ''; }), + nga.field('onlineState').cssClasses(nodeClasses), nga.field('monitoringState').cssClasses(nodeClasses).template(function (node) { switch (node.values.monitoringState) { case 'active': @@ -158,11 +174,24 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest .exportFields([]) .fields([ nga.field('id').cssClasses(monitoringStateClasses), + nga.field('hostname').cssClasses(monitoringStateClasses), nga.field('mac').cssClasses(monitoringStateClasses), + nga.field('monitoring_state').cssClasses(monitoringStateClasses).template(function (monitoringState) { + switch (monitoringState.values.monitoring_state) { + case 'active': + return ''; + + case 'pending': + return ''; + + default: + return ''; + } + }), nga.field('state').cssClasses(monitoringStateClasses), nga.field('last_seen').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('import_timestamp').label('Imported').map(formatMoment).cssClasses(monitoringStateClasses), - nga.field('last_status_mail_type').cssClasses(monitoringStateClasses), + nga.field('last_status_mail_type').map(nullable).cssClasses(monitoringStateClasses), nga.field('last_status_mail_sent').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('created_at').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('modified_at').map(formatMoment).cssClasses(monitoringStateClasses) diff --git a/server/db/patches/005_add_hostname_and_monitoring_state.sql b/server/db/patches/005_add_hostname_and_monitoring_state.sql new file mode 100644 index 0000000..243b1ef --- /dev/null +++ b/server/db/patches/005_add_hostname_and_monitoring_state.sql @@ -0,0 +1,2 @@ +ALTER TABLE node_state ADD COLUMN hostname VARCHAR(32); +ALTER TABLE node_state ADD COLUMN monitoring_state VARCHAR(10); diff --git a/server/jobs/nodeInformationRetrievalJob.js b/server/jobs/nodeInformationRetrievalJob.js index 133e65b..0248f30 100644 --- a/server/jobs/nodeInformationRetrievalJob.js +++ b/server/jobs/nodeInformationRetrievalJob.js @@ -2,7 +2,7 @@ angular.module('ffffng').factory('NodeInformationRetrievalJob', function (MonitoringService, Logger) { return { - description: 'Fetches the nodes.json and calculates and stores the monitoring status for nodes with active monitoring.', + description: 'Fetches the nodes.json and calculates and stores the monitoring / online status for registered nodes.', run: function (callback) { MonitoringService.retrieveNodeInformation(function (err) { diff --git a/server/main.js b/server/main.js index 45fe71e..3c18306 100755 --- a/server/main.js +++ b/server/main.js @@ -22,6 +22,7 @@ require('./app'); require('./router'); require('./libs'); +require('./utils/databaseUtil'); require('./utils/errorTypes'); require('./utils/resources'); require('./utils/strings'); diff --git a/server/resources/nodeResource.js b/server/resources/nodeResource.js index dc63d56..01fdd52 100644 --- a/server/resources/nodeResource.js +++ b/server/resources/nodeResource.js @@ -3,8 +3,11 @@ angular.module('ffffng').factory('NodeResource', function ( Constraints, Validator, + Logger, + MonitoringService, NodeService, _, + deepExtend, Strings, Resources, ErrorTypes @@ -110,22 +113,62 @@ angular.module('ffffng').factory('NodeResource', function ( return node.token; }); - var filteredNodes = Resources.filter( - realNodes, - ['hostname', 'nickname', 'email', 'token', 'mac', 'key'], - restParams - ); - var total = filteredNodes.length; + var macs = _.map(realNodes, function (node) { + return node.mac; + }); - var sortedNodes = Resources.sort( - filteredNodes, - ['hostname', 'nickname', 'email', 'token', 'mac', 'key', 'coords', 'monitoringState'], - restParams - ); - var pageNodes = Resources.getPageEntities(sortedNodes, restParams); + MonitoringService.getByMacs(macs, function (err, nodeStateByMac) { + if (err) { + Logger.tag('nodes', 'admin').error('Error getting nodes by MACs:', err); + return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError}); + } - res.set('X-Total-Count', total); - return Resources.success(res, pageNodes); + var enhancedNodes = _.map(realNodes, function (node) { + var nodeState = nodeStateByMac[node.mac]; + if (nodeState) { + return deepExtend({}, node, { + onlineState: nodeState.state + }); + } + + return node; + }); + + var filteredNodes = Resources.filter( + enhancedNodes, + [ + 'hostname', + 'nickname', + 'email', + 'token', + 'mac', + 'key', + 'onlineState' + ], + restParams + ); + var total = filteredNodes.length; + + var sortedNodes = Resources.sort( + filteredNodes, + [ + 'hostname', + 'nickname', + 'email', + 'token', + 'mac', + 'key', + 'coords', + 'onlineState', + 'monitoringState' + ], + restParams + ); + var pageNodes = Resources.getPageEntities(sortedNodes, restParams); + + res.set('X-Total-Count', total); + return Resources.success(res, pageNodes); + }); }); }); } diff --git a/server/services/monitoringService.js b/server/services/monitoringService.js index a18eda3..63e48f9 100644 --- a/server/services/monitoringService.js +++ b/server/services/monitoringService.js @@ -5,7 +5,9 @@ angular.module('ffffng') _, async, config, + deepExtend, Database, + DatabaseUtil, ErrorTypes, Logger, moment, @@ -34,10 +36,12 @@ angular.module('ffffng') return Database.run( 'INSERT INTO node_state ' + - '(mac, state, last_seen, import_timestamp, (last_status_mail_sent, last_status_mail_type) OR last_status_mail_type IS NULL) ' + + '(hostname, mac, monitoring_state, state, last_seen, import_timestamp, last_status_mail_sent, last_status_mail_type) ' + 'VALUES (?, ?, ?, ?, ?, ?)', [ + node.hostname, node.mac, + node.monitoringState, nodeData.state, nodeData.lastSeen.unix(), nodeData.importTimestamp.unix(), @@ -66,45 +70,37 @@ angular.module('ffffng') return Database.run( 'UPDATE node_state ' + - 'SET state = ?, last_seen = ?, import_timestamp = ?, modified_at = ?' + + 'SET hostname = ?, monitoring_state = ?, state = ?, last_seen = ?, import_timestamp = ?, modified_at = ?' + 'WHERE id = ? AND mac = ?', [ - nodeData.state, nodeData.lastSeen.unix(), nodeData.importTimestamp.unix(), moment().unix(), - row.id, node.mac + node.hostname, + node.monitoringState, + nodeData.state, + nodeData.lastSeen.unix(), + nodeData.importTimestamp.unix(), + moment().unix(), + + row.id, + node.mac ], callback ); } - function deleteNodeInformation(nodeData, node, callback) { - Logger - .tag('monitoring', 'information-retrieval') - .debug('Node is not being monitored, deleting monitoring data: %s', nodeData.mac); - return Database.run( - 'DELETE FROM node_state WHERE mac = ? AND import_timestamp < ?', - [node.mac, nodeData.importTimestamp.unix()], - callback - ); - } - function storeNodeInformation(nodeData, node, callback) { - if (node.monitoring && node.monitoringConfirmed) { - Logger.tag('monitoring', 'information-retrieval').debug('Node is being monitored: %s', nodeData.mac); + Logger.tag('monitoring', 'information-retrieval').debug('Storing status for node: %s', nodeData.mac); - return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) { - if (err) { - return callback(err); - } + return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) { + if (err) { + return callback(err); + } - if (_.isUndefined(row)) { - return insertNodeInformation(nodeData, node, callback); - } else { - return updateNodeInformation(nodeData, node, row, callback); - } - }); - } else { - return deleteNodeInformation(nodeData, node, callback); - } + if (_.isUndefined(row)) { + return insertNodeInformation(nodeData, node, callback); + } else { + return updateNodeInformation(nodeData, node, row, callback); + } + }); } var isValidMac = Validator.forConstraint(Constraints.node.mac); @@ -221,7 +217,7 @@ angular.module('ffffng') function (nodeState, mailCallback) { var mac = nodeState.mac; Logger.tag('monitoring', 'mail-sending').debug('Loading node data for: %s', mac); - NodeService.findNodeDataByMac(mac, function (err, node, nodeSecrets) { + NodeService.getNodeDataByMac(mac, function (err, node, nodeSecrets) { if (err) { Logger .tag('monitoring', 'mail-sending') @@ -267,10 +263,10 @@ angular.module('ffffng') var now = moment().unix(); Database.run( 'UPDATE node_state ' + - 'SET modified_at = ?, last_status_mail_sent = ?, last_status_mail_type = ?' + + 'SET hostname = ?, monitoring_state = ?, modified_at = ?, last_status_mail_sent = ?, last_status_mail_type = ?' + 'WHERE id = ?', [ - now, now, mailType, + node.hostname, node.monitoringState, now, now, mailType, nodeState.id ], mailCallback @@ -357,7 +353,9 @@ angular.module('ffffng') getAll: function (restParams, callback) { var sortFields = [ 'id', + 'hostname', 'mac', + 'monitoring_state', 'state', 'last_seen', 'import_timestamp', @@ -367,7 +365,9 @@ angular.module('ffffng') 'modified_at' ]; var filterFields = [ + 'hostname', 'mac', + 'monitoring_state', 'state', 'last_status_mail_type' ]; @@ -406,6 +406,31 @@ angular.module('ffffng') ); }, + getByMacs: function (macs, callback) { + if (_.isEmpty(macs)) { + return callback(null, {}); + } + + var inCondition = DatabaseUtil.inCondition('mac', macs); + + Database.all( + 'SELECT * FROM node_state WHERE ' + inCondition.query, + _.concat([], inCondition.params), + function (err, rows) { + if (err) { + return callback(err); + } + + var nodeStateByMac = {}; + _.each(rows, function (row) { + nodeStateByMac[row.mac] = row; + }); + + callback(null, nodeStateByMac); + } + ); + }, + confirm: function (token, callback) { NodeService.getNodeDataByMonitoringToken(token, function (err, node, nodeSecrets) { if (err) { @@ -490,7 +515,7 @@ angular.module('ffffng') function (nodeData, nodeCallback) { Logger.tag('monitoring', 'information-retrieval').debug('Importing: %s', nodeData.mac); - NodeService.findNodeDataByMac(nodeData.mac, function (err, node) { + NodeService.getNodeDataByMac(nodeData.mac, function (err, node) { if (err) { Logger .tag('monitoring', 'information-retrieval') diff --git a/server/services/nodeService.js b/server/services/nodeService.js index 6c8558a..3d8a21e 100644 --- a/server/services/nodeService.js +++ b/server/services/nodeService.js @@ -424,7 +424,7 @@ angular.module('ffffng') getAllNodes: function (callback) { findNodeFiles({}, function (err, files) { if (err) { - Logger.tag('nodes').error('Error getting all nodes:', error); + Logger.tag('nodes').error('Error getting all nodes:', err); return callback({data: 'Internal error.', type: ErrorTypes.internalError}); } @@ -434,7 +434,7 @@ angular.module('ffffng') parseNodeFile, function (err, nodes) { if (err) { - Logger.tag('nodes').error('Error getting all nodes:', error); + Logger.tag('nodes').error('Error getting all nodes:', err); return callback({data: 'Internal error.', type: ErrorTypes.internalError}); } @@ -444,7 +444,7 @@ angular.module('ffffng') }); }, - findNodeDataByMac: function (mac, callback) { + getNodeDataByMac: function (mac, callback) { return findNodeDataByFilePattern({ mac: mac }, callback); }, diff --git a/server/utils/databaseUtil.js b/server/utils/databaseUtil.js new file mode 100644 index 0000000..5f1ea96 --- /dev/null +++ b/server/utils/databaseUtil.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('ffffng').factory('DatabaseUtil', function (_) { + return { + inCondition: function (field, list) { + return { + query: '(' + field + ' IN (' + _.join(_.times(list.length, _.constant('?')), ', ') + '))', + params: list + }; + } + }; +});