Implement filter in devices page
This commit is contained in:
1 parent
eda43083ec
commit
ccf548081f
7 files changed
+198
-47
No files matched your search
@@ -2,6 +2,23 @@
|
||||
|
||||
export default {
|
||||
pageSize: 10,
|
||||
filter: [
|
||||
{
|
||||
label: "Serial number",
|
||||
parameter: "DeviceID.SerialNumber",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
label: "Product class",
|
||||
parameter: "DeviceID.ProductClass",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
label: "Tag",
|
||||
parameter: "tag",
|
||||
type: "string"
|
||||
}
|
||||
],
|
||||
index: [
|
||||
{
|
||||
label: "Serial number",
|
||||
|
||||
@@ -200,3 +200,13 @@ button {
|
||||
color: var(--color2);
|
||||
}
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin-bottom: 0.5em;
|
||||
& > input {
|
||||
padding: 2px;
|
||||
width: 30em;
|
||||
font-size: 90%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
+49
-8
@@ -3,13 +3,14 @@
|
||||
import m from "mithril";
|
||||
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
|
||||
function load(filter, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
m
|
||||
.request({
|
||||
url: "/api/devices/",
|
||||
data: { filter: JSON.stringify(filter), limit: limit }
|
||||
data: { filter: filter, limit: limit }
|
||||
})
|
||||
.then(res => {
|
||||
resolve(res);
|
||||
@@ -29,8 +30,9 @@ function count(filter, limit) {
|
||||
m
|
||||
.request({
|
||||
method: "HEAD",
|
||||
url: "/api/devices/",
|
||||
data: { filter: JSON.stringify(filter), limit: limit },
|
||||
url:
|
||||
"/api/devices/?" +
|
||||
m.buildQueryString({ filter: filter, limit: limit }),
|
||||
extract: extract
|
||||
})
|
||||
.then(res => {
|
||||
@@ -43,9 +45,45 @@ function count(filter, limit) {
|
||||
});
|
||||
}
|
||||
|
||||
function preprocessFilter(filter) {
|
||||
if (Array.isArray(filter)) {
|
||||
filter = filter.map(f => {
|
||||
f = f.trim();
|
||||
if (f.startsWith("(")) return f;
|
||||
|
||||
let match = f.match(/([^=<>]+)([=<>]+)(.+)/);
|
||||
|
||||
let param = match[1].trim();
|
||||
let op = match[2].trim();
|
||||
let value = match[3].trim();
|
||||
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (err) {
|
||||
// eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
for (let cf of config.filter)
|
||||
if (param === cf.label) param = cf.parameter;
|
||||
|
||||
if (param === "tag") {
|
||||
let tag = value.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
if (op === "=") return `Tags.${tag} is not null`;
|
||||
else if (op === "<>") return `Tags.${tag} is null`;
|
||||
}
|
||||
|
||||
return `${param} ${op} ${JSON.stringify(value)}`;
|
||||
});
|
||||
filter = "(" + filter.join(") AND (") + ")";
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
function init(args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Promise.all([count(args.filter), load(args.filter, config.pageSize)])
|
||||
let filter = preprocessFilter(args.filter);
|
||||
|
||||
Promise.all([count(filter), load(filter, config.pageSize)])
|
||||
.then(res => {
|
||||
resolve({ filter: args.filter, total: res[0], devices: res[1] });
|
||||
})
|
||||
@@ -93,15 +131,18 @@ const component = {
|
||||
document.title = "Devices - GenieACS";
|
||||
function showMore() {
|
||||
const lastDevice = vnode.attrs.devices[vnode.attrs.devices.length - 1];
|
||||
let f = Object.assign({}, vnode.attrs.filter, {
|
||||
"DeviceID.ID>": lastDevice["DeviceID.ID"].value[0]
|
||||
});
|
||||
load(f, config.pageSize).then(res => {
|
||||
let f = (vnode.attrs.filter || []).concat(
|
||||
`DeviceID.ID > "${lastDevice["DeviceID.ID"].value[0]}"`
|
||||
);
|
||||
load(preprocessFilter(f), config.pageSize).then(res => {
|
||||
vnode.attrs.devices = vnode.attrs.devices.concat(res);
|
||||
if (res.length < config.pageSize)
|
||||
vnode.attrs.total = vnode.attrs.devices.length;
|
||||
});
|
||||
}
|
||||
return [
|
||||
m("h1", "Listing devices"),
|
||||
m(filterComponent, { filterList: [].concat(vnode.attrs.filter) }),
|
||||
renderTable(
|
||||
vnode.attrs.devices,
|
||||
config.index,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import config from "./config";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
let a = m(
|
||||
"datalist#filters",
|
||||
config.filter.map(f => m("option", { value: `${f.label} = ` }))
|
||||
);
|
||||
|
||||
const filterList = vnode.attrs.filterList.filter(f => {
|
||||
return !!f;
|
||||
});
|
||||
filterList.push("");
|
||||
|
||||
return m(
|
||||
"div.filter",
|
||||
[m("b", "Filter"), a].concat(
|
||||
filterList.map((fltr, idx) => {
|
||||
return m("input", {
|
||||
type: "string",
|
||||
list: "filters",
|
||||
value: fltr,
|
||||
onchange: function(e) {
|
||||
e.redraw = false;
|
||||
filterList[idx] = this.value;
|
||||
m.route.set("/devices", {
|
||||
filter: filterList.filter(f => !!f)
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
+54
-19
@@ -7,34 +7,68 @@ const querystring = require("querystring");
|
||||
|
||||
const config = require("./config");
|
||||
const device = require("./device");
|
||||
const filterParser = require("./filter-parser");
|
||||
|
||||
const commaAscii = ",".charCodeAt(0);
|
||||
const newLineAscii = "\n".charCodeAt(0);
|
||||
|
||||
function filtersToQuery(filters) {
|
||||
if (!Array.isArray(filters)) filters = [filters];
|
||||
function filterToQuery(filter, negate = false, res = {}) {
|
||||
const op = filter[0];
|
||||
|
||||
let queries = [];
|
||||
for (let filter of filters) {
|
||||
let q = {};
|
||||
for (let [k, v] of Object.entries(filter)) {
|
||||
let [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
|
||||
if (!op || op === "=") q[param] = v;
|
||||
else if (op === ">") q[param] = { $gt: v };
|
||||
else throw new Error(`Operator "${op}" not recognized`);
|
||||
if ((!negate && op === "AND") || (negate && op === "OR")) {
|
||||
for (let i = 1; i < filter.length; ++i)
|
||||
filterToQuery(filter[i], negate, res);
|
||||
} else if ((!negate && op === "OR") || (negate && op === "AND")) {
|
||||
res["$or"] = res["$or"] || [];
|
||||
|
||||
for (let i = 1; i < filter.length; ++i)
|
||||
res["$or"].push(filterToQuery(filter[i], negate));
|
||||
} else if (op === "NOT") {
|
||||
filterToQuery(filter[1], !negate, res);
|
||||
} else if (op === "=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) p["$ne"] = filter[2];
|
||||
else p["$eq"] = filter[2];
|
||||
} else if (op === "<>") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) {
|
||||
p["$eq"] = filter[2];
|
||||
} else {
|
||||
p["$ne"] = filter[2];
|
||||
p["$exists"] = true;
|
||||
}
|
||||
queries.push(q);
|
||||
} else if (op === ">") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$gt"] = filter[2];
|
||||
} else if (op === ">=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$gte"] = filter[2];
|
||||
} else if (op === "<") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$lt"] = filter[2];
|
||||
} else if (op === "<=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$lte"] = filter[2];
|
||||
} else if (op === "IS NULL") {
|
||||
res[filter[1]] = { $exists: negate };
|
||||
} else if (op === "IS NOT NULL") {
|
||||
res[filter[1]] = { $exists: !negate };
|
||||
} else {
|
||||
throw new Error(`Unrecognized operator ${op}`);
|
||||
}
|
||||
|
||||
if (queries.length === 1) return queries[0];
|
||||
else return { $or: queries };
|
||||
return res;
|
||||
}
|
||||
|
||||
function count(resource, filters, limit) {
|
||||
function count(resource, filter, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let qs = {};
|
||||
if (filters) {
|
||||
let q = filtersToQuery(filters);
|
||||
if (filter) {
|
||||
let q = filterToQuery(filterParser.parse(filter));
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
qs.query = JSON.stringify(q);
|
||||
}
|
||||
@@ -62,14 +96,15 @@ function count(resource, filters, limit) {
|
||||
});
|
||||
}
|
||||
|
||||
function query(resource, filters, limit, callback) {
|
||||
function query(resource, filter, limit, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let ret;
|
||||
if (!callback) ret = [];
|
||||
let qs = {};
|
||||
if (filters) {
|
||||
let q = filtersToQuery(filters);
|
||||
if (filter) {
|
||||
let q = filterToQuery(filterParser.parse(filter));
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
|
||||
qs.query = JSON.stringify(q);
|
||||
}
|
||||
|
||||
|
||||
+4
-18
@@ -17,14 +17,7 @@ const resources = {
|
||||
for (let [resource, update] of Object.entries(resources)) {
|
||||
router.head(`/${resource}`, async ctx => {
|
||||
let filter, limit;
|
||||
if (ctx.request.query.filter)
|
||||
if (Array.isArray(ctx.request.query.filter))
|
||||
filter = ctx.request.query.query.map(q => JSON.parse(q));
|
||||
else {
|
||||
filter = JSON.parse(ctx.request.query.filter);
|
||||
if (!Array.isArray(filter)) filter = [filter];
|
||||
}
|
||||
|
||||
if (ctx.request.query.filter) filter = ctx.request.query.filter;
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
|
||||
let count = await apiFunctions.count(resource, filter, limit);
|
||||
@@ -34,28 +27,21 @@ for (let [resource, update] of Object.entries(resources)) {
|
||||
|
||||
router.get(`/${resource}`, async ctx => {
|
||||
let filter, limit;
|
||||
if (ctx.request.query.filter)
|
||||
if (Array.isArray(ctx.request.query.filter))
|
||||
filter = ctx.request.query.query.map(q => JSON.parse(q));
|
||||
else {
|
||||
filter = JSON.parse(ctx.request.query.filter);
|
||||
if (!Array.isArray(filter)) filter = [filter];
|
||||
}
|
||||
|
||||
if (ctx.request.query.filter) filter = ctx.request.query.filter;
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
|
||||
ctx.body = await apiFunctions.query(resource, filter, limit);
|
||||
});
|
||||
|
||||
router.head(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = { _id: ctx.params.id };
|
||||
let filter = `DeviceID.ID = "${ctx.params.id}"`;
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
ctx.body = "";
|
||||
});
|
||||
|
||||
router.get(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = { _id: ctx.params.id };
|
||||
let filter = `DeviceID.ID = "${ctx.params.id}"`;
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
ctx.body = res[0];
|
||||
|
||||
+23
-2
@@ -142,8 +142,29 @@ function transposeQuery(query) {
|
||||
const newQuery = {};
|
||||
|
||||
for (let [key, val] of Object.entries(query))
|
||||
if (key.startsWith("$")) newQuery[key] = transposeQuery(val);
|
||||
else if (key === "DeviceID.ID") newQuery["_id"] = val;
|
||||
if (key.startsWith("$")) {
|
||||
newQuery[key] = val.map(v => transposeQuery(v));
|
||||
} else if (key === "DeviceID.ID") {
|
||||
newQuery["_id"] = val;
|
||||
} else if (key === "DeviceID.SerialNumber") {
|
||||
newQuery["_deviceId._SerialNumber"] = val;
|
||||
} else if (key === "DeviceID.OUI") {
|
||||
newQuery["_deviceId._OUI"] = val;
|
||||
} else if (key === "DeviceID.Manufacturer") {
|
||||
newQuery["_deviceId._Manufacturer"] = val;
|
||||
} else if (key.startsWith("Tags.")) {
|
||||
if (val["$exists"] === true) {
|
||||
newQuery["_tags"] = newQuery["_tags"] || {};
|
||||
newQuery["_tags"]["$all"] = newQuery["_tags"]["$all"] || [];
|
||||
newQuery["_tags"]["$all"].push(key.slice(5));
|
||||
} else if (val["$exists"] === false) {
|
||||
newQuery["_tags"] = newQuery["_tags"] || {};
|
||||
newQuery["_tags"]["$nin"] = newQuery["_tags"]["$nin"] || [];
|
||||
newQuery["_tags"]["$nin"].push(key.slice(5));
|
||||
}
|
||||
} else {
|
||||
newQuery[key] = val;
|
||||
}
|
||||
|
||||
return newQuery;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user