Filtering of nodes in admin panel.

This commit is contained in:
baldo 2016-07-23 11:30:41 +02:00
parent 26e6b53333
commit 8660553d0f
9 changed files with 176 additions and 76 deletions

View file

@ -122,14 +122,44 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
}) })
]) ])
.filters([ .filters([
nga.field('q') nga.field('q', 'template')
.label('') .label('')
.pinned(true) .pinned(true)
.template( .template(
'<div class="input-group">' + '<div class="input-group">' +
'<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' + '<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>' +
'<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'), '<span class="input-group-addon"><i class="fa fa-search"></i></span></div>'),
nga.field('hasKey', 'choice')
.label('VPN key')
.pinned(false)
.choices([
{ label: 'VPN key set', value: true },
{ label: 'no VPN key', value: false }
]),
nga.field('hasCoords', 'choice')
.label('GPS coordinates')
.pinned(false)
.choices([
{ label: 'coordinates set', value: true },
{ label: 'no coordinates', value: false }
]),
nga.field('onlineState', 'choice')
.label('Online state')
.pinned(false)
.choices([
{ label: 'online', value: 'ONLINE' },
{ label: 'offline', value: 'OFFLINE' }
]),
nga.field('monitoringState', 'choice')
.label('Monitoring')
.pinned(false)
.choices([
{ label: 'pending', value: 'pending' },
{ label: 'active', value: 'active' },
{ label: 'disabled', value: 'disabled' }
])
]) ])
.actions(['<ma-filter-button filters="filters()" enabled-filters="enabledFilters" enable-filter="enableFilter()"></ma-filter-button>'])
.listActions( .listActions(
'<ma-edit-button entry="entry" entity="entity" size="sm"></ma-edit-button> ' + '<ma-edit-button entry="entry" entity="entity" size="sm"></ma-edit-button> ' +
'<ma-delete-button entry="entry" entity="entity" size="sm"></ma-delete-button> ' + '<ma-delete-button entry="entry" entity="entity" size="sm"></ma-delete-button> ' +

View file

@ -2,87 +2,95 @@
<h1>Dashboard / Statistics</h1> <h1>Dashboard / Statistics</h1>
</div> </div>
<h2><a ui-sref="list({entity:'nodes'})">Nodes</a></h2> <h2><a ui-sref="list({entity: 'nodes'})">Nodes</a></h2>
<div class="row"> <div class="row">
<div class="col-lg-3"> <div class="col-lg-3">
<div class="panel panel-primary"> <a ui-sref="list({entity: 'nodes'})">
<div class="panel-heading"> <div class="panel panel-primary">
<div class="row"> <div class="panel-heading">
<div class="col-xs-3"> <div class="row">
<i class="fa fa-circle-o fa-5x"></i> <div class="col-xs-3">
</div> <i class="fa fa-circle-o fa-5x"></i>
<div class="col-xs-9 text-right"> </div>
<div class="huge">{{ stats.nodes.registered }}</div> <div class="col-xs-9 text-right">
<div>Total Registered</div> <div class="huge">{{ stats.nodes.registered }}</div>
<div>Total Registered</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </a>
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<div class="panel panel-yellow"> <a ui-sref="list({entity: 'nodes', search: {hasKey: true}})">
<div class="panel-heading"> <div class="panel panel-yellow">
<div class="row"> <div class="panel-heading">
<div class="col-xs-3"> <div class="row">
<i class="fa fa-lock fa-5x"></i> <div class="col-xs-3">
</div> <i class="fa fa-lock fa-5x"></i>
<div class="col-xs-9 text-right"> </div>
<div class="huge">{{ stats.nodes.withVPN }}</div> <div class="col-xs-9 text-right">
<div>With VPN-Key</div> <div class="huge">{{ stats.nodes.withVPN }}</div>
<div>With VPN-Key</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </a>
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<div class="panel panel-green"> <a ui-sref="list({entity: 'nodes', search: {hasCoords: true}})">
<div class="panel-heading"> <div class="panel panel-green">
<div class="row"> <div class="panel-heading">
<div class="col-xs-3"> <div class="row">
<i class="fa fa-map-marker fa-5x"></i> <div class="col-xs-3">
</div> <i class="fa fa-map-marker fa-5x"></i>
<div class="col-xs-9 text-right"> </div>
<div class="huge">{{ stats.nodes.withCoords }}</div> <div class="col-xs-9 text-right">
<div>With Coordinates</div> <div class="huge">{{ stats.nodes.withCoords }}</div>
<div>With Coordinates</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </a>
</div> </div>
</div> </div>
<h2><a ui-sref="list({entity:'monitoring'})">Monitoring</a></h2>
<div class="row"> <div class="row">
<div class="col-lg-3"> <a ui-sref="list({entity: 'nodes', search: {monitoringState: 'active'}})">
<div class="panel panel-green"> <div class="col-lg-3">
<div class="panel-heading"> <div class="panel panel-green">
<div class="row"> <div class="panel-heading">
<div class="col-xs-3"> <div class="row">
<i class="fa fa-heartbeat fa-5x"></i> <div class="col-xs-3">
</div> <i class="fa fa-heartbeat fa-5x"></i>
<div class="col-xs-9 text-right"> </div>
<div class="huge">{{ stats.nodes.monitoring.active }}</div> <div class="col-xs-9 text-right">
<div>Active</div> <div class="huge">{{ stats.nodes.monitoring.active }}</div>
<div>Monitoring active</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </a>
<div class="col-lg-3"> <a ui-sref="list({entity: 'nodes', search: {monitoringState: 'pending'}})">
<div class="panel panel-red"> <div class="col-lg-3">
<div class="panel-heading"> <div class="panel panel-red">
<div class="row"> <div class="panel-heading">
<div class="col-xs-3"> <div class="row">
<i class="fa fa-envelope fa-5x"></i> <div class="col-xs-3">
</div> <i class="fa fa-envelope fa-5x"></i>
<div class="col-xs-9 text-right"> </div>
<div class="huge">{{ stats.nodes.monitoring.pending }}</div> <div class="col-xs-9 text-right">
<div>Pending</div> <div class="huge">{{ stats.nodes.monitoring.pending }}</div>
<div>Monitoring pending</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </a>
</div> </div>

View file

@ -44,7 +44,7 @@ angular.module('ffffng').factory('MailResource', function (
}, },
getAll: function (req, res) { getAll: function (req, res) {
Resources.getValidRestParams('list', req, function (err, restParams) { Resources.getValidRestParams('list', null, req, function (err, restParams) {
if (err) { if (err) {
return Resources.error(res, err); return Resources.error(res, err);
} }

View file

@ -14,7 +14,7 @@ angular.module('ffffng').factory('MonitoringResource', function (
return { return {
getAll: function (req, res) { getAll: function (req, res) {
Resources.getValidRestParams('list', req, function (err, restParams) { Resources.getValidRestParams('list', null, req, function (err, restParams) {
if (err) { if (err) {
return Resources.error(res, err); return Resources.error(res, err);
} }

View file

@ -98,7 +98,7 @@ angular.module('ffffng').factory('NodeResource', function (
}, },
getAll: function (req, res) { getAll: function (req, res) {
Resources.getValidRestParams('list', req, function (err, restParams) { Resources.getValidRestParams('list', 'node', req, function (err, restParams) {
if (err) { if (err) {
return Resources.error(res, err); return Resources.error(res, err);
} }

View file

@ -75,7 +75,7 @@ angular.module('ffffng').factory('TaskResource', function (
return { return {
getAll: function (req, res) { getAll: function (req, res) {
Resources.getValidRestParams('list', req, function (err, restParams) { Resources.getValidRestParams('list', null, req, function (err, restParams) {
if (err) { if (err) {
return Resources.error(res, err); return Resources.error(res, err);
} }

View file

@ -263,7 +263,7 @@ angular.module('ffffng')
var pending = value === 'pending'; var pending = value === 'pending';
node.monitoring = active || pending; node.monitoring = active || pending;
node.monitoringConfirmed = active; node.monitoringConfirmed = active;
node.monitoringState = active ? 'active' : (pending ? 'pending' : ''); node.monitoringState = active ? 'active' : (pending ? 'pending' : 'disabled');
} else if (key === 'monitoringToken') { } else if (key === 'monitoringToken') {
nodeSecrets.monitoringToken = value; nodeSecrets.monitoringToken = value;
} else { } else {

View file

@ -70,45 +70,64 @@ angular.module('ffffng').factory('Resources', function (_, Constraints, Validato
}; };
} }
function getConstrainedValues(data, constraints) {
var values = {};
_.each(_.keys(constraints), function (key) {
var value = data[key];
values[key] = _.isUndefined(value) && !_.isUndefined(constraints[key].default)
? constraints[key].default
: value;
});
return values;
}
return { return {
getData: function (req) { getData: function (req) {
return _.extend({}, req.body, req.params, req.query); return _.extend({}, req.body, req.params, req.query);
}, },
getValidRestParams: function(type, req, callback) { getValidRestParams: function(type, subtype, req, callback) {
var constraints = Constraints.rest[type]; var constraints = Constraints.rest[type];
if (!_.isPlainObject(constraints)) { if (!_.isPlainObject(constraints)) {
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type); Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
return callback({data: 'Internal error.', type: ErrorTypes.internalError}); return callback({data: 'Internal error.', type: ErrorTypes.internalError});
} }
var filterConstraints = {};
if (subtype) {
filterConstraints = Constraints[subtype + 'Filters'];
if (!_.isPlainObject(filterConstraints)) {
Logger.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
}
}
var data = this.getData(req); var data = this.getData(req);
var restParams = {}; var restParams = getConstrainedValues(data, constraints);
_.each(_.keys(constraints), function (key) { var filterParams = getConstrainedValues(data, filterConstraints);
var value = data[key];
restParams[key] = _.isUndefined(value) && !_.isUndefined(constraints[key].default)
? constraints[key].default
: value;
});
var areValidParams = Validator.forConstraints(constraints); var areValidParams = Validator.forConstraints(constraints);
if (!areValidParams(restParams)) { var areValidFilters = Validator.forConstraints(filterConstraints);
if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest}); return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest});
} }
restParams.filters = filterParams;
callback(null, restParams); callback(null, restParams);
}, },
filter: function (entities, allowedFilterFields, restParams) { filter: function (entities, allowedFilterFields, restParams) {
var query = restParams.q; var query = restParams.q;
if (!query) { if (query) {
return entities; query = _.toLower(query.trim());
} }
query = _.toLower(query.trim()); function queryMatches(entity) {
if (!query) {
return _.filter(entities, function (entity) { return true;
}
return _.some(allowedFilterFields, function (field) { return _.some(allowedFilterFields, function (field) {
var value = entity[field]; var value = entity[field];
if (_.isNumber(value)) { if (_.isNumber(value)) {
@ -125,7 +144,30 @@ angular.module('ffffng').factory('Resources', function (_, Constraints, Validato
} }
return _.includes(value, query); return _.includes(value, query);
}) });
}
var filters = restParams.filters;
function filtersMatch(entity) {
if (_.isEmpty(filters)) {
return true;
}
return _.every(filters, function (value, key) {
if (_.isUndefined(value)) {
return true;
}
if (_.startsWith(key, 'has')) {
var entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
return _.isEmpty(entity[entityKey]).toString() !== value;
}
return entity[key] === value;
});
}
return _.filter(entities, function (entity) {
return queryMatches(entity) && filtersMatch(entity);
}); });
}, },

View file

@ -47,6 +47,26 @@ angular.module('ffffng').constant('Constraints', {
optional: false optional: false
} }
}, },
nodeFilters: {
hasKey: {
type: 'boolean',
optional: true
},
hasCoords: {
type: 'boolean',
optional: true
},
onlineState: {
type: 'string',
regex: /^(ONLINE|OFFLINE)$/,
optional: true
},
monitoringState: {
type: 'string',
regex: /^(disabled|active|pending)$/,
optional: true
}
},
rest: { rest: {
list: { list: {
_page: { _page: {