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([
nga.field('q')
nga.field('q', 'template')
.label('')
.pinned(true)
.template(
'<div class="input-group">' +
'<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>'),
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(
'<ma-edit-button entry="entry" entity="entity" size="sm"></ma-edit-button> ' +
'<ma-delete-button entry="entry" entity="entity" size="sm"></ma-delete-button> ' +

View file

@ -6,6 +6,7 @@
<div class="row">
<div class="col-lg-3">
<a ui-sref="list({entity: 'nodes'})">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="row">
@ -19,8 +20,10 @@
</div>
</div>
</div>
</a>
</div>
<div class="col-lg-3">
<a ui-sref="list({entity: 'nodes', search: {hasKey: true}})">
<div class="panel panel-yellow">
<div class="panel-heading">
<div class="row">
@ -34,8 +37,10 @@
</div>
</div>
</div>
</a>
</div>
<div class="col-lg-3">
<a ui-sref="list({entity: 'nodes', search: {hasCoords: true}})">
<div class="panel panel-green">
<div class="panel-heading">
<div class="row">
@ -49,12 +54,12 @@
</div>
</div>
</div>
</a>
</div>
</div>
<h2><a ui-sref="list({entity:'monitoring'})">Monitoring</a></h2>
<div class="row">
<a ui-sref="list({entity: 'nodes', search: {monitoringState: 'active'}})">
<div class="col-lg-3">
<div class="panel panel-green">
<div class="panel-heading">
@ -64,12 +69,14 @@
</div>
<div class="col-xs-9 text-right">
<div class="huge">{{ stats.nodes.monitoring.active }}</div>
<div>Active</div>
<div>Monitoring active</div>
</div>
</div>
</div>
</div>
</div>
</a>
<a ui-sref="list({entity: 'nodes', search: {monitoringState: 'pending'}})">
<div class="col-lg-3">
<div class="panel panel-red">
<div class="panel-heading">
@ -79,10 +86,11 @@
</div>
<div class="col-xs-9 text-right">
<div class="huge">{{ stats.nodes.monitoring.pending }}</div>
<div>Pending</div>
<div>Monitoring pending</div>
</div>
</div>
</div>
</div>
</div>
</a>
</div>

View file

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

View file

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

View file

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

View file

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

View file

@ -263,7 +263,7 @@ angular.module('ffffng')
var pending = value === 'pending';
node.monitoring = active || pending;
node.monitoringConfirmed = active;
node.monitoringState = active ? 'active' : (pending ? 'pending' : '');
node.monitoringState = active ? 'active' : (pending ? 'pending' : 'disabled');
} else if (key === 'monitoringToken') {
nodeSecrets.monitoringToken = value;
} 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 {
getData: function (req) {
return _.extend({}, req.body, req.params, req.query);
},
getValidRestParams: function(type, req, callback) {
getValidRestParams: function(type, subtype, req, callback) {
var constraints = Constraints.rest[type];
if (!_.isPlainObject(constraints)) {
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
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 restParams = {};
_.each(_.keys(constraints), function (key) {
var value = data[key];
restParams[key] = _.isUndefined(value) && !_.isUndefined(constraints[key].default)
? constraints[key].default
: value;
});
var restParams = getConstrainedValues(data, constraints);
var filterParams = getConstrainedValues(data, filterConstraints);
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});
}
restParams.filters = filterParams;
callback(null, restParams);
},
filter: function (entities, allowedFilterFields, restParams) {
var query = restParams.q;
if (!query) {
return entities;
if (query) {
query = _.toLower(query.trim());
}
query = _.toLower(query.trim());
return _.filter(entities, function (entity) {
function queryMatches(entity) {
if (!query) {
return true;
}
return _.some(allowedFilterFields, function (field) {
var value = entity[field];
if (_.isNumber(value)) {
@ -125,7 +144,30 @@ angular.module('ffffng').factory('Resources', function (_, Constraints, Validato
}
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
}
},
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: {
list: {
_page: {