Display of online state for nodes and hostname for monitoring info in admin panel.

This commit is contained in:
baldo 2016-06-26 19:09:12 +02:00
parent c0ae97e298
commit cd746a41ea
9 changed files with 168 additions and 56 deletions

View file

@ -47,11 +47,11 @@
color: lightgrey; color: lightgrey;
} }
.monitoring-state-online { .node-online, .monitoring-state-online {
color: green; color: green;
} }
.monitoring-state-offline { .node-offline, .monitoring-state-offline {
color: red; color: red;
} }

View file

@ -15,6 +15,10 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
return { params: params }; return { params: params };
}); });
function nullable(value) {
return value ? value : 'N/A';
}
function formatMoment(unix) { function formatMoment(unix) {
return unix ? moment.unix(unix).fromNow() : 'N/A'; return unix ? moment.unix(unix).fromNow() : 'N/A';
} }
@ -41,8 +45,19 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
if (!node) { if (!node) {
return; return;
} }
switch (node.values.onlineState) {
case 'ONLINE':
return 'node-online';
case 'OFFLINE':
return 'node-offline';
default:
return; return;
} }
}
var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token')); var nodes = nga.entity('nodes').label('Nodes').identifier(nga.field('token'));
nodes nodes
@ -70,6 +85,7 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
? '<i class="fa fa-map-marker coords-set" aria-hidden="true" title="coordinates set"></i>' ? '<i class="fa fa-map-marker coords-set" aria-hidden="true" title="coordinates set"></i>'
: '<i class="fa fa-times coords-unset" aria-hidden="true" title="no coordinates"></i>'; : '<i class="fa fa-times coords-unset" aria-hidden="true" title="no coordinates"></i>';
}), }),
nga.field('onlineState').cssClasses(nodeClasses),
nga.field('monitoringState').cssClasses(nodeClasses).template(function (node) { nga.field('monitoringState').cssClasses(nodeClasses).template(function (node) {
switch (node.values.monitoringState) { switch (node.values.monitoringState) {
case 'active': case 'active':
@ -158,11 +174,24 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
.exportFields([]) .exportFields([])
.fields([ .fields([
nga.field('id').cssClasses(monitoringStateClasses), nga.field('id').cssClasses(monitoringStateClasses),
nga.field('hostname').cssClasses(monitoringStateClasses),
nga.field('mac').cssClasses(monitoringStateClasses), nga.field('mac').cssClasses(monitoringStateClasses),
nga.field('monitoring_state').cssClasses(monitoringStateClasses).template(function (monitoringState) {
switch (monitoringState.values.monitoring_state) {
case 'active':
return '<i class="fa fa-heartbeat monitoring-active" title="active"></i>';
case 'pending':
return '<i class="fa fa-envelope monitoring-confirmation-pending" title="confirmation pending"></i>';
default:
return '<i class="fa fa-times monitoring-disabled" title="disabled"></i>';
}
}),
nga.field('state').cssClasses(monitoringStateClasses), nga.field('state').cssClasses(monitoringStateClasses),
nga.field('last_seen').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('last_seen').map(formatMoment).cssClasses(monitoringStateClasses),
nga.field('import_timestamp').label('Imported').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('last_status_mail_sent').map(formatMoment).cssClasses(monitoringStateClasses),
nga.field('created_at').map(formatMoment).cssClasses(monitoringStateClasses), nga.field('created_at').map(formatMoment).cssClasses(monitoringStateClasses),
nga.field('modified_at').map(formatMoment).cssClasses(monitoringStateClasses) nga.field('modified_at').map(formatMoment).cssClasses(monitoringStateClasses)

View file

@ -0,0 +1,2 @@
ALTER TABLE node_state ADD COLUMN hostname VARCHAR(32);
ALTER TABLE node_state ADD COLUMN monitoring_state VARCHAR(10);

View file

@ -2,7 +2,7 @@
angular.module('ffffng').factory('NodeInformationRetrievalJob', function (MonitoringService, Logger) { angular.module('ffffng').factory('NodeInformationRetrievalJob', function (MonitoringService, Logger) {
return { 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) { run: function (callback) {
MonitoringService.retrieveNodeInformation(function (err) { MonitoringService.retrieveNodeInformation(function (err) {

View file

@ -22,6 +22,7 @@ require('./app');
require('./router'); require('./router');
require('./libs'); require('./libs');
require('./utils/databaseUtil');
require('./utils/errorTypes'); require('./utils/errorTypes');
require('./utils/resources'); require('./utils/resources');
require('./utils/strings'); require('./utils/strings');

View file

@ -3,8 +3,11 @@
angular.module('ffffng').factory('NodeResource', function ( angular.module('ffffng').factory('NodeResource', function (
Constraints, Constraints,
Validator, Validator,
Logger,
MonitoringService,
NodeService, NodeService,
_, _,
deepExtend,
Strings, Strings,
Resources, Resources,
ErrorTypes ErrorTypes
@ -110,16 +113,55 @@ angular.module('ffffng').factory('NodeResource', function (
return node.token; return node.token;
}); });
var macs = _.map(realNodes, function (node) {
return node.mac;
});
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});
}
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( var filteredNodes = Resources.filter(
realNodes, enhancedNodes,
['hostname', 'nickname', 'email', 'token', 'mac', 'key'], [
'hostname',
'nickname',
'email',
'token',
'mac',
'key',
'onlineState'
],
restParams restParams
); );
var total = filteredNodes.length; var total = filteredNodes.length;
var sortedNodes = Resources.sort( var sortedNodes = Resources.sort(
filteredNodes, filteredNodes,
['hostname', 'nickname', 'email', 'token', 'mac', 'key', 'coords', 'monitoringState'], [
'hostname',
'nickname',
'email',
'token',
'mac',
'key',
'coords',
'onlineState',
'monitoringState'
],
restParams restParams
); );
var pageNodes = Resources.getPageEntities(sortedNodes, restParams); var pageNodes = Resources.getPageEntities(sortedNodes, restParams);
@ -128,6 +170,7 @@ angular.module('ffffng').factory('NodeResource', function (
return Resources.success(res, pageNodes); return Resources.success(res, pageNodes);
}); });
}); });
});
} }
}; };
}); });

View file

@ -5,7 +5,9 @@ angular.module('ffffng')
_, _,
async, async,
config, config,
deepExtend,
Database, Database,
DatabaseUtil,
ErrorTypes, ErrorTypes,
Logger, Logger,
moment, moment,
@ -34,10 +36,12 @@ angular.module('ffffng')
return Database.run( return Database.run(
'INSERT INTO node_state ' + '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 (?, ?, ?, ?, ?, ?)', 'VALUES (?, ?, ?, ?, ?, ?)',
[ [
node.hostname,
node.mac, node.mac,
node.monitoringState,
nodeData.state, nodeData.state,
nodeData.lastSeen.unix(), nodeData.lastSeen.unix(),
nodeData.importTimestamp.unix(), nodeData.importTimestamp.unix(),
@ -66,30 +70,25 @@ angular.module('ffffng')
return Database.run( return Database.run(
'UPDATE node_state ' + '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 = ?', 'WHERE id = ? AND mac = ?',
[ [
nodeData.state, nodeData.lastSeen.unix(), nodeData.importTimestamp.unix(), moment().unix(), node.hostname,
row.id, node.mac node.monitoringState,
nodeData.state,
nodeData.lastSeen.unix(),
nodeData.importTimestamp.unix(),
moment().unix(),
row.id,
node.mac
], ],
callback 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) { function storeNodeInformation(nodeData, node, callback) {
if (node.monitoring && node.monitoringConfirmed) { Logger.tag('monitoring', 'information-retrieval').debug('Storing status for node: %s', nodeData.mac);
Logger.tag('monitoring', 'information-retrieval').debug('Node is being monitored: %s', nodeData.mac);
return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) { return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) {
if (err) { if (err) {
@ -102,9 +101,6 @@ angular.module('ffffng')
return updateNodeInformation(nodeData, node, row, callback); return updateNodeInformation(nodeData, node, row, callback);
} }
}); });
} else {
return deleteNodeInformation(nodeData, node, callback);
}
} }
var isValidMac = Validator.forConstraint(Constraints.node.mac); var isValidMac = Validator.forConstraint(Constraints.node.mac);
@ -221,7 +217,7 @@ angular.module('ffffng')
function (nodeState, mailCallback) { function (nodeState, mailCallback) {
var mac = nodeState.mac; var mac = nodeState.mac;
Logger.tag('monitoring', 'mail-sending').debug('Loading node data for: %s', 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) { if (err) {
Logger Logger
.tag('monitoring', 'mail-sending') .tag('monitoring', 'mail-sending')
@ -267,10 +263,10 @@ angular.module('ffffng')
var now = moment().unix(); var now = moment().unix();
Database.run( Database.run(
'UPDATE node_state ' + '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 = ?', 'WHERE id = ?',
[ [
now, now, mailType, node.hostname, node.monitoringState, now, now, mailType,
nodeState.id nodeState.id
], ],
mailCallback mailCallback
@ -357,7 +353,9 @@ angular.module('ffffng')
getAll: function (restParams, callback) { getAll: function (restParams, callback) {
var sortFields = [ var sortFields = [
'id', 'id',
'hostname',
'mac', 'mac',
'monitoring_state',
'state', 'state',
'last_seen', 'last_seen',
'import_timestamp', 'import_timestamp',
@ -367,7 +365,9 @@ angular.module('ffffng')
'modified_at' 'modified_at'
]; ];
var filterFields = [ var filterFields = [
'hostname',
'mac', 'mac',
'monitoring_state',
'state', 'state',
'last_status_mail_type' '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) { confirm: function (token, callback) {
NodeService.getNodeDataByMonitoringToken(token, function (err, node, nodeSecrets) { NodeService.getNodeDataByMonitoringToken(token, function (err, node, nodeSecrets) {
if (err) { if (err) {
@ -490,7 +515,7 @@ angular.module('ffffng')
function (nodeData, nodeCallback) { function (nodeData, nodeCallback) {
Logger.tag('monitoring', 'information-retrieval').debug('Importing: %s', nodeData.mac); 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) { if (err) {
Logger Logger
.tag('monitoring', 'information-retrieval') .tag('monitoring', 'information-retrieval')

View file

@ -424,7 +424,7 @@ angular.module('ffffng')
getAllNodes: function (callback) { getAllNodes: function (callback) {
findNodeFiles({}, function (err, files) { findNodeFiles({}, function (err, files) {
if (err) { 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}); return callback({data: 'Internal error.', type: ErrorTypes.internalError});
} }
@ -434,7 +434,7 @@ angular.module('ffffng')
parseNodeFile, parseNodeFile,
function (err, nodes) { function (err, nodes) {
if (err) { 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}); 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); return findNodeDataByFilePattern({ mac: mac }, callback);
}, },

View file

@ -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
};
}
};
});