Filtering of nodes in admin panel.
This commit is contained in:
parent
26e6b53333
commit
8660553d0f
|
@ -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> ' +
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Reference in a new issue