Implement autocomplete and smart queries
This commit is contained in:
1 parent
518254aa55
commit
5b84b1daad
10 files changed
+331
-47
No files matched your search
@@ -0,0 +1,113 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
class Autocomplete {
|
||||||
|
constructor(className, callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.element = null;
|
||||||
|
this.hideTimeout = null;
|
||||||
|
this.visible = false;
|
||||||
|
this.default = null;
|
||||||
|
|
||||||
|
this.container = document.createElement("div");
|
||||||
|
this.container.style.position = "absolute";
|
||||||
|
this.container.style.display = "block";
|
||||||
|
this.container.style.opacity = 0;
|
||||||
|
this.container.className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(el) {
|
||||||
|
el.setAttribute("autocomplete", "off");
|
||||||
|
|
||||||
|
el.addEventListener("focus", () => {
|
||||||
|
this.element = el;
|
||||||
|
let domRect = el.getBoundingClientRect();
|
||||||
|
this.container.style.left = `${domRect.left}px`;
|
||||||
|
this.container.style.width = `${domRect.width}px`;
|
||||||
|
this.container.style.top = `${domRect.bottom}px`;
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener("blur", () => {
|
||||||
|
if (this.element !== el) return;
|
||||||
|
if (!this.visible) return;
|
||||||
|
this.default = null;
|
||||||
|
this.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener("keydown", e => {
|
||||||
|
if (this.element !== el) return;
|
||||||
|
if (e.key === "Escape") this.hide();
|
||||||
|
else if (e.key === "Enter")
|
||||||
|
if (this.default != null) {
|
||||||
|
el.value = this.default;
|
||||||
|
e.preventDefault();
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener("input", () => {
|
||||||
|
if (this.element !== el) return;
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.container.style.opacity = 0;
|
||||||
|
this.visible = false;
|
||||||
|
this.default = null;
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
this.hideTimeout = setTimeout(() => {
|
||||||
|
this.hideTimeout = null;
|
||||||
|
while (this.container.firstChild)
|
||||||
|
this.container.removeChild(this.container.firstChild);
|
||||||
|
document.body.removeChild(this.container);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
const el = this.element;
|
||||||
|
|
||||||
|
this.callback(el.value, suggestions => {
|
||||||
|
if (this.element !== el) return;
|
||||||
|
this.default = null;
|
||||||
|
|
||||||
|
if (!suggestions.length) {
|
||||||
|
if (this.visible) this.hide();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.container.firstChild)
|
||||||
|
this.container.removeChild(this.container.firstChild);
|
||||||
|
|
||||||
|
if (!this.visible) {
|
||||||
|
if (!this.hideTimeout) {
|
||||||
|
document.body.appendChild(this.container);
|
||||||
|
window.getComputedStyle(this.container).opacity;
|
||||||
|
} else {
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
this.hideTimeout = null;
|
||||||
|
}
|
||||||
|
this.container.style.opacity = 1;
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.default = suggestions[0];
|
||||||
|
|
||||||
|
for (let suggestion of suggestions) {
|
||||||
|
let e = document.createElement("div");
|
||||||
|
e.className = "suggestion";
|
||||||
|
let t = document.createTextNode(suggestion);
|
||||||
|
e.appendChild(t);
|
||||||
|
e.addEventListener("mousedown", ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
el.value = suggestion;
|
||||||
|
if (this.element === el) this.update(el);
|
||||||
|
});
|
||||||
|
this.container.appendChild(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Autocomplete;
|
||||||
@@ -587,3 +587,19 @@ span.tag {
|
|||||||
.overlay > .put-form {
|
.overlay > .put-form {
|
||||||
min-width: 700px;
|
min-width: 700px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
box-shadow: 0 0 10px #ccc;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid var(--color1);
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: opacity var(--fade);
|
||||||
|
& > .suggestion {
|
||||||
|
padding: 5px;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-4
@@ -10,6 +10,8 @@ import * as taskQueue from "./task-queue";
|
|||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
import * as expression from "../common/expression";
|
import * as expression from "../common/expression";
|
||||||
import memoize from "../common/memoize";
|
import memoize from "../common/memoize";
|
||||||
|
import * as smartQuery from "./smart-query";
|
||||||
|
import * as expressionParser from "../common/expression-parser";
|
||||||
|
|
||||||
const PAGE_SIZE = config.ui.pageSize || 10;
|
const PAGE_SIZE = config.ui.pageSize || 10;
|
||||||
|
|
||||||
@@ -39,6 +41,14 @@ const getChildAttrs = memoize((attrs, device) =>
|
|||||||
Object.assign({}, attrs, { device: device })
|
Object.assign({}, attrs, { device: device })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const unpackSmartQuery = memoize(query => {
|
||||||
|
return expressionParser.map(query, e => {
|
||||||
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
|
return smartQuery.unpack("devices", e[2], e[3]);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!window.authorizer.hasAccess("devices", 2))
|
if (!window.authorizer.hasAccess("devices", 2))
|
||||||
@@ -347,9 +357,8 @@ const component = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
||||||
const filter = vnode.attrs.filter
|
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
|
||||||
? memoizedParse(vnode.attrs.filter)
|
filter = unpackSmartQuery(filter);
|
||||||
: true;
|
|
||||||
|
|
||||||
let devs = store.fetch("devices", filter, {
|
let devs = store.fetch("devices", filter, {
|
||||||
limit: vnode.state.showCount || PAGE_SIZE,
|
limit: vnode.state.showCount || PAGE_SIZE,
|
||||||
@@ -372,7 +381,7 @@ const component = {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing devices"),
|
m("h1", "Listing devices"),
|
||||||
m(filterComponent, {
|
m(filterComponent, {
|
||||||
predefined: Object.values(config.ui.filters),
|
resource: "devices",
|
||||||
filter: vnode.attrs.filter,
|
filter: vnode.attrs.filter,
|
||||||
onChange: onFilterChanged
|
onChange: onFilterChanged
|
||||||
}),
|
}),
|
||||||
|
|||||||
+13
-10
@@ -7,6 +7,8 @@ import * as store from "./store";
|
|||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
import * as expression from "../common/expression";
|
import * as expression from "../common/expression";
|
||||||
import memoize from "../common/memoize";
|
import memoize from "../common/memoize";
|
||||||
|
import * as smartQuery from "./smart-query";
|
||||||
|
import * as expressionParser from "../common/expression-parser";
|
||||||
|
|
||||||
const PAGE_SIZE = config.ui.pageSize || 10;
|
const PAGE_SIZE = config.ui.pageSize || 10;
|
||||||
|
|
||||||
@@ -33,6 +35,14 @@ const getDownloadUrl = memoize(filter => {
|
|||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const unpackSmartQuery = memoize(query => {
|
||||||
|
return expressionParser.map(query, e => {
|
||||||
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
|
return smartQuery.unpack("faults", e[2], e[3]);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
if (!window.authorizer.hasAccess("faults", 2))
|
if (!window.authorizer.hasAccess("faults", 2))
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
@@ -219,9 +229,8 @@ const component = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
||||||
const filter = vnode.attrs.filter
|
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
|
||||||
? memoizedParse(vnode.attrs.filter)
|
filter = unpackSmartQuery(filter);
|
||||||
: true;
|
|
||||||
|
|
||||||
let faults = store.fetch("faults", filter, {
|
let faults = store.fetch("faults", filter, {
|
||||||
limit: vnode.state.showCount || PAGE_SIZE,
|
limit: vnode.state.showCount || PAGE_SIZE,
|
||||||
@@ -240,13 +249,7 @@ const component = {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing faults"),
|
m("h1", "Listing faults"),
|
||||||
m(filterComponent, {
|
m(filterComponent, {
|
||||||
predefined: [
|
resource: "faults",
|
||||||
{ parameter: "device" },
|
|
||||||
{ parameter: "channel" },
|
|
||||||
{ parameter: "code" },
|
|
||||||
{ parameter: "retries" },
|
|
||||||
{ parameter: "timestamp" }
|
|
||||||
],
|
|
||||||
filter: vnode.attrs.filter,
|
filter: vnode.attrs.filter,
|
||||||
onChange: onFilterChanged
|
onChange: onFilterChanged
|
||||||
}),
|
}),
|
||||||
|
|||||||
+33
-11
@@ -3,14 +3,40 @@
|
|||||||
import m from "mithril";
|
import m from "mithril";
|
||||||
import * as expression from "../common/expression";
|
import * as expression from "../common/expression";
|
||||||
import memoize from "../common/memoize";
|
import memoize from "../common/memoize";
|
||||||
|
import Autocomplete from "./autocomplete-compnent";
|
||||||
|
import * as smartQuery from "./smart-query";
|
||||||
|
|
||||||
|
const getAutocomplete = memoize(resource => {
|
||||||
|
const labels = smartQuery.getLabels(resource);
|
||||||
|
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
|
||||||
|
txt = txt.toLowerCase();
|
||||||
|
cb(labels.filter(s => s.toLowerCase().includes(txt)).map(s => `${s}: `));
|
||||||
|
});
|
||||||
|
return autocomplete;
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseFilter(f) {
|
||||||
|
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
||||||
|
let k = f.split(":", 1)[0];
|
||||||
|
let v = f.slice(k.length + 1).trim();
|
||||||
|
return ["FUNC", "Q", k.trim(), v];
|
||||||
|
}
|
||||||
|
return expression.parse(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyFilter(f) {
|
||||||
|
if (Array.isArray(f) && f[0] === "FUNC" && f[1] === "Q")
|
||||||
|
return `${f[2]}: ${f[3]}`;
|
||||||
|
return expression.stringify(f);
|
||||||
|
}
|
||||||
|
|
||||||
const splitFilter = memoize(filter => {
|
const splitFilter = memoize(filter => {
|
||||||
if (!filter) return [""];
|
if (!filter) return [""];
|
||||||
const list = [];
|
const list = [];
|
||||||
const f = expression.parse(filter);
|
const f = expression.parse(filter);
|
||||||
if (Array.isArray(f) && f[0] === "AND")
|
if (Array.isArray(f) && f[0] === "AND")
|
||||||
for (let ff of f.slice(1)) list.push(expression.stringify(ff));
|
for (let ff of f.slice(1)) list.push(stringifyFilter(ff));
|
||||||
else list.push(expression.stringify(f));
|
else list.push(stringifyFilter(f));
|
||||||
|
|
||||||
list.push("");
|
list.push("");
|
||||||
return list;
|
return list;
|
||||||
@@ -18,12 +44,6 @@ const splitFilter = memoize(filter => {
|
|||||||
|
|
||||||
const component = {
|
const component = {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
const predefined = vnode.attrs.predefined || [];
|
|
||||||
let a = m(
|
|
||||||
"datalist#filters",
|
|
||||||
predefined.map(f => m("option", { value: `${f.parameter} = ` }))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!vnode.state.filterList || vnode.attrs.filter !== vnode.state.filter) {
|
if (!vnode.state.filterList || vnode.attrs.filter !== vnode.state.filter) {
|
||||||
vnode.state.filterInvalid = 0;
|
vnode.state.filterInvalid = 0;
|
||||||
vnode.state.filter = vnode.attrs.filter;
|
vnode.state.filter = vnode.attrs.filter;
|
||||||
@@ -35,7 +55,7 @@ const component = {
|
|||||||
vnode.state.filterList = vnode.state.filterList.filter(f => f);
|
vnode.state.filterList = vnode.state.filterList.filter(f => f);
|
||||||
let filter = vnode.state.filterList.map((f, idx) => {
|
let filter = vnode.state.filterList.map((f, idx) => {
|
||||||
try {
|
try {
|
||||||
return expression.parse(f);
|
return parseFilter(f);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vnode.state.filterInvalid |= 1 << idx;
|
vnode.state.filterInvalid |= 1 << idx;
|
||||||
}
|
}
|
||||||
@@ -56,17 +76,19 @@ const component = {
|
|||||||
|
|
||||||
return m(
|
return m(
|
||||||
"div.filter",
|
"div.filter",
|
||||||
[m("b", "Filter"), a].concat(
|
[m("b", "Filter")].concat(
|
||||||
vnode.state.filterList.map((fltr, idx) => {
|
vnode.state.filterList.map((fltr, idx) => {
|
||||||
return m("input", {
|
return m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
list: "filters",
|
|
||||||
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
|
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
|
||||||
value: fltr,
|
value: fltr,
|
||||||
onchange: e => {
|
onchange: e => {
|
||||||
vnode.state.filterList = vnode.state.filterList.slice();
|
vnode.state.filterList = vnode.state.filterList.slice();
|
||||||
vnode.state.filterList[idx] = e.target.value.trim();
|
vnode.state.filterList[idx] = e.target.value.trim();
|
||||||
onChange();
|
onChange();
|
||||||
|
},
|
||||||
|
oncreate: vn => {
|
||||||
|
getAutocomplete(vnode.attrs.resource).attach(vn.dom);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ function render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("keydown", e => {
|
document.addEventListener("keydown", e => {
|
||||||
if (e.keyCode === 27 && close(overlayCallback)) m.redraw();
|
if (overlayCallback && e.keyCode === 27 && close(overlayCallback)) m.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("popstate", () => {
|
||||||
|
|||||||
+13
-8
@@ -10,6 +10,8 @@ import * as expression from "../common/expression";
|
|||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
import putForm from "./components/put-form";
|
import putForm from "./components/put-form";
|
||||||
import memoize from "../common/memoize";
|
import memoize from "../common/memoize";
|
||||||
|
import * as smartQuery from "./smart-query";
|
||||||
|
import * as expressionParser from "../common/expression-parser";
|
||||||
|
|
||||||
const PAGE_SIZE = config.ui.pageSize || 10;
|
const PAGE_SIZE = config.ui.pageSize || 10;
|
||||||
|
|
||||||
@@ -27,6 +29,14 @@ const attributes = [
|
|||||||
{ id: "provisionArgs", label: "Arguments", unsortable: true }
|
{ id: "provisionArgs", label: "Arguments", unsortable: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const unpackSmartQuery = memoize(query => {
|
||||||
|
return expressionParser.map(query, e => {
|
||||||
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
|
return smartQuery.unpack("presets", e[2], e[3]);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function putActoinHandler(action, object, isNew) {
|
function putActoinHandler(action, object, isNew) {
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
let id = object["_id"];
|
let id = object["_id"];
|
||||||
@@ -352,9 +362,8 @@ const component = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
||||||
const filter = vnode.attrs.filter
|
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
|
||||||
? memoizedParse(vnode.attrs.filter)
|
filter = unpackSmartQuery(filter);
|
||||||
: true;
|
|
||||||
|
|
||||||
let presets = store.fetch("presets", filter, {
|
let presets = store.fetch("presets", filter, {
|
||||||
limit: vnode.state.showCount || PAGE_SIZE,
|
limit: vnode.state.showCount || PAGE_SIZE,
|
||||||
@@ -384,11 +393,7 @@ const component = {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing presets"),
|
m("h1", "Listing presets"),
|
||||||
m(filterComponent, {
|
m(filterComponent, {
|
||||||
predefined: [
|
resource: "presets",
|
||||||
{ parameter: "_id" },
|
|
||||||
{ parameter: "channel" },
|
|
||||||
{ parameter: "weight" }
|
|
||||||
],
|
|
||||||
filter: vnode.attrs.filter,
|
filter: vnode.attrs.filter,
|
||||||
onChange: onFilterChanged
|
onChange: onFilterChanged
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import * as notifications from "./notifications";
|
|||||||
import memoize from "../common/memoize";
|
import memoize from "../common/memoize";
|
||||||
import putForm from "./components/put-form";
|
import putForm from "./components/put-form";
|
||||||
import * as overlay from "./overlay";
|
import * as overlay from "./overlay";
|
||||||
|
import * as smartQuery from "./smart-query";
|
||||||
|
import * as expressionParser from "../common/expression-parser";
|
||||||
|
|
||||||
const PAGE_SIZE = config.ui.pageSize || 10;
|
const PAGE_SIZE = config.ui.pageSize || 10;
|
||||||
|
|
||||||
@@ -21,6 +23,14 @@ const attributes = [
|
|||||||
{ id: "script", label: "Script", type: "code" }
|
{ id: "script", label: "Script", type: "code" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const unpackSmartQuery = memoize(query => {
|
||||||
|
return expressionParser.map(query, e => {
|
||||||
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
|
return smartQuery.unpack("provisions", e[2], e[3]);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function putActoinHandler(action, object, isNew) {
|
function putActoinHandler(action, object, isNew) {
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
let id = object["_id"];
|
let id = object["_id"];
|
||||||
@@ -333,9 +343,8 @@ const component = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
||||||
const filter = vnode.attrs.filter
|
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
|
||||||
? memoizedParse(vnode.attrs.filter)
|
filter = unpackSmartQuery(filter);
|
||||||
: true;
|
|
||||||
|
|
||||||
let provisions = store.fetch("provisions", filter, {
|
let provisions = store.fetch("provisions", filter, {
|
||||||
limit: vnode.state.showCount || PAGE_SIZE,
|
limit: vnode.state.showCount || PAGE_SIZE,
|
||||||
@@ -356,7 +365,7 @@ const component = {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing provisions"),
|
m("h1", "Listing provisions"),
|
||||||
m(filterComponent, {
|
m(filterComponent, {
|
||||||
predefined: [{ parameter: "_id" }],
|
resource: "provisions",
|
||||||
filter: vnode.attrs.filter,
|
filter: vnode.attrs.filter,
|
||||||
onChange: onFilterChanged
|
onChange: onFilterChanged
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import config from "./config";
|
||||||
|
import * as expression from "../common/expression";
|
||||||
|
|
||||||
|
let resources = {
|
||||||
|
devices: {},
|
||||||
|
faults: {
|
||||||
|
Device: { parameter: ["PARAM", "device"], type: "string" },
|
||||||
|
Channel: { parameter: ["PARAM", "channel"], type: "string" },
|
||||||
|
Code: { parameter: ["PARAM", "code"], type: "string" },
|
||||||
|
Retries: { parameter: ["PARAM", "retries"], type: "number" },
|
||||||
|
Timestamp: { parameter: ["PARAM", "timestamp"], type: "timestamp" }
|
||||||
|
},
|
||||||
|
presets: {
|
||||||
|
ID: { parameter: ["PARAM", "_id"], type: "string" },
|
||||||
|
Channel: { parameter: ["PARAM", "channel"], type: "string" },
|
||||||
|
Weight: { parameter: ["PARAM", "weight"], type: "number" }
|
||||||
|
},
|
||||||
|
provisions: {
|
||||||
|
ID: { parameter: ["PARAM", "_id"], type: "string" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let v of Object.values(config.ui.filters))
|
||||||
|
resources.devices[v.label] = {
|
||||||
|
parameter: expression.parse(v.parameter),
|
||||||
|
type: (v.type || "").split(",").map(s => s.trim())
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLabels(resource) {
|
||||||
|
if (!resources[resource]) return [];
|
||||||
|
return Object.keys(resources[resource]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryNumber(param, value) {
|
||||||
|
let op = "=";
|
||||||
|
for (let o of ["<>", "=", "<=", "<", ">=", ">"])
|
||||||
|
if (value.startsWith(o)) {
|
||||||
|
op = o;
|
||||||
|
value = value.slice(o.length).trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = parseInt(value);
|
||||||
|
if (v !== +value) return null;
|
||||||
|
|
||||||
|
return [op, param, v];
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryString(param, value) {
|
||||||
|
return ["LIKE", ["FUNC", "LOWER", param], value.toLowerCase()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryMac(param, value) {
|
||||||
|
value = value.replace(/[^a-f0-9]/gi, "").toLowerCase();
|
||||||
|
if (!value) return null;
|
||||||
|
if (value.length === 12)
|
||||||
|
return [
|
||||||
|
"LIKE",
|
||||||
|
["FUNC", "LOWER", param],
|
||||||
|
value.replace(/(..)(?!$)/g, "$1:")
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
"OR",
|
||||||
|
[
|
||||||
|
"LIKE",
|
||||||
|
["FUNC", "LOWER", param],
|
||||||
|
`%${value.replace(/(..)(?!$)/g, "$1:")}%`
|
||||||
|
],
|
||||||
|
["LIKE", ["FUNC", "LOWER", param], `%${value.replace(/(.)(.)/g, "$1:$2")}%`]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpack(resource, label, value) {
|
||||||
|
if (!resources[resource]) return null;
|
||||||
|
const type = resources[resource][label].type;
|
||||||
|
value = value.trim();
|
||||||
|
let res = ["OR"];
|
||||||
|
|
||||||
|
if (type.length === 0 || type.includes("number")) {
|
||||||
|
let q = queryNumber(resources[resource][label].parameter, value);
|
||||||
|
if (q) res.push(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.length === 0 || type.includes("string")) {
|
||||||
|
let q = queryString(resources[resource][label].parameter, value);
|
||||||
|
if (q) res.push(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.includes("mac")) {
|
||||||
|
let q = queryMac(resources[resource][label].parameter, value);
|
||||||
|
if (q) res.push(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.length <= 1) return null;
|
||||||
|
else if (res.length === 2) return res[1];
|
||||||
|
else return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getLabels, unpack };
|
||||||
@@ -29,15 +29,20 @@ function processDeviceFilter(filter) {
|
|||||||
else if (exp[0] === "IS NOT NULL") return ["=", ["PARAM", "_tags"], t];
|
else if (exp[0] === "IS NOT NULL") return ["=", ["PARAM", "_tags"], t];
|
||||||
else if (exp[0] === "=" && exp[2] === true)
|
else if (exp[0] === "=" && exp[2] === true)
|
||||||
return ["=", ["PARAM", "_tags"], t];
|
return ["=", ["PARAM", "_tags"], t];
|
||||||
} else if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) {
|
} else if (
|
||||||
let e = exp.slice();
|
["=", "<>", ">", ">=", "<", "<=", "LIKE", "NOT LIKE"].includes(exp[0])
|
||||||
if (
|
) {
|
||||||
Array.isArray(exp[1]) &&
|
let e = expressionParser.map(exp, ee => {
|
||||||
exp[1][0] === "PARAM" &&
|
if (
|
||||||
typeof exp[1][1] === "string" &&
|
Array.isArray(ee) &&
|
||||||
!exp[1][1].startsWith("_")
|
ee[0] === "PARAM" &&
|
||||||
)
|
typeof ee[1] === "string" &&
|
||||||
e[1] = ["PARAM", `${exp[1][1]}._value`];
|
!ee[1].startsWith("_")
|
||||||
|
)
|
||||||
|
return ["PARAM", `${ee[1]}._value`];
|
||||||
|
return ee;
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof e[2] === "number") {
|
if (typeof e[2] === "number") {
|
||||||
let alt = e.slice();
|
let alt = e.slice();
|
||||||
alt[2] = new Date(e[2]);
|
alt[2] = new Date(e[2]);
|
||||||
|
|||||||
Reference in new issue
Block a user