Filtering of nodes in admin panel.
This commit is contained in:
parent
26e6b53333
commit
8660553d0f
9 changed files with 176 additions and 76 deletions
|
@ -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> ' +
|
||||
|
|
|
@ -2,87 +2,95 @@
|
|||
<h1>Dashboard / Statistics</h1>
|
||||
</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="col-lg-3">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-circle-o fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.registered }}</div>
|
||||
<div>Total Registered</div>
|
||||
<a ui-sref="list({entity: 'nodes'})">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-circle-o fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.registered }}</div>
|
||||
<div>Total Registered</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-yellow">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-lock fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.withVPN }}</div>
|
||||
<div>With VPN-Key</div>
|
||||
<a ui-sref="list({entity: 'nodes', search: {hasKey: true}})">
|
||||
<div class="panel panel-yellow">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-lock fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.withVPN }}</div>
|
||||
<div>With VPN-Key</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-green">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-map-marker fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.withCoords }}</div>
|
||||
<div>With Coordinates</div>
|
||||
<a ui-sref="list({entity: 'nodes', search: {hasCoords: true}})">
|
||||
<div class="panel panel-green">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-map-marker fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.withCoords }}</div>
|
||||
<div>With Coordinates</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2><a ui-sref="list({entity:'monitoring'})">Monitoring</a></h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-green">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-heartbeat fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.monitoring.active }}</div>
|
||||
<div>Active</div>
|
||||
<a ui-sref="list({entity: 'nodes', search: {monitoringState: 'active'}})">
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-green">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-heartbeat fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.monitoring.active }}</div>
|
||||
<div>Monitoring active</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-red">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-envelope fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.monitoring.pending }}</div>
|
||||
<div>Pending</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">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<i class="fa fa-envelope fa-5x"></i>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<div class="huge">{{ stats.nodes.monitoring.pending }}</div>
|
||||
<div>Monitoring pending</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue