From a7e07cbde96da7fb4191d678a063e9165da714b1 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 19 Jan 2018 16:21:00 -0800 Subject: [PATCH] Improve devices page filter --- client/css/app.css | 16 +++++++++ client/devices-page.js | 66 ++++++++++++++------------------------ client/filter-component.js | 50 ++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/client/css/app.css b/client/css/app.css index 75ae270..66d33ac 100644 --- a/client/css/app.css +++ b/client/css/app.css @@ -69,6 +69,9 @@ i { display: inline-block; border-top-left-radius: 6px; border-top-right-radius: 6px; + border-top: 1px solid var(--color1); + border-left: 1px solid var(--color1); + border-right: 1px solid var(--color1); transition: background-color var(--fade); &.active, &:hover { @@ -208,5 +211,18 @@ button { width: 30em; font-size: 90%; display: block; + border: 1px solid var(--color1); + background-color: var(--color3); + margin: 2px; + border-radius: 4px; + transition: border-color var(--fade), background-color var(--fade); + &.error { + background-color: #efcbcf; + border-color: #e6b2b7; + } + &:focus, + &:hover { + border-color: var(--color4); + } } } diff --git a/client/devices-page.js b/client/devices-page.js index ea6faba..192f049 100644 --- a/client/devices-page.js +++ b/client/devices-page.js @@ -4,6 +4,7 @@ import m from "mithril"; import config from "./config"; import filterComponent from "./filter-component"; +import * as filterParser from "../common/filter-parser"; function load(filter, limit) { return new Promise((resolve, reject) => { @@ -45,47 +46,14 @@ 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) => { - let filter = preprocessFilter(args.filter); + let filter; + if (args.filter) filter = filterParser.parse(args.filter); - Promise.all([count(filter), load(filter, config.pageSize)]) + Promise.all([count(args.filter), load(args.filter, config.pageSize)]) .then(res => { - resolve({ filter: args.filter, total: res[0], devices: res[1] }); + resolve({ filter: filter, total: res[0], devices: res[1] }); }) .catch(reject); }); @@ -129,20 +97,34 @@ function renderTable(devices, parameters, total, showMoreCallback) { const component = { view: vnode => { document.title = "Devices - GenieACS"; + function showMore() { const lastDevice = vnode.attrs.devices[vnode.attrs.devices.length - 1]; - let f = (vnode.attrs.filter || []).concat( - `DeviceID.ID > "${lastDevice["DeviceID.ID"].value[0]}"` - ); - load(preprocessFilter(f), config.pageSize).then(res => { + let f = filterParser.and(vnode.attrs.filter, [ + ">", + "DeviceID.ID", + lastDevice["DeviceID.ID"].value[0] + ]); + + load(filterParser.stringify(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; }); } + + function onFilterChanged(filter) { + let params = {}; + if (filter) params.filter = filterParser.stringify(filter); + m.route.set("/devices", params); + } + return [ m("h1", "Listing devices"), - m(filterComponent, { filterList: [].concat(vnode.attrs.filter) }), + m(filterComponent, { + filter: vnode.attrs.filter, + onChange: onFilterChanged + }), renderTable( vnode.attrs.devices, config.index, diff --git a/client/filter-component.js b/client/filter-component.js index 8f2f401..220b721 100644 --- a/client/filter-component.js +++ b/client/filter-component.js @@ -3,33 +3,59 @@ import m from "mithril"; import config from "./config"; +import * as filterParser from "../common/filter-parser"; const component = { view: vnode => { let a = m( "datalist#filters", - config.filter.map(f => m("option", { value: `${f.label} = ` })) + config.filter.map(f => m("option", { value: `${f.parameter} = ` })) ); - const filterList = vnode.attrs.filterList.filter(f => { - return !!f; - }); - filterList.push(""); + if (!vnode.state.filterList) { + vnode.state.filterList = []; + if (vnode.attrs.filter) + if (vnode.attrs.filter[0] === "AND") + for (let i = 1; i < vnode.attrs.filter.length; ++i) + vnode.state.filterList.push( + filterParser.stringify(vnode.attrs.filter[i]) + ); + else + vnode.state.filterList.push( + filterParser.stringify(vnode.attrs.filter) + ); + vnode.state.filterList.push(""); + } + + function onChange() { + vnode.state.filterInvalid = 0; + vnode.state.filterList = vnode.state.filterList.filter( + f => f && f.trim() + ); + let filter = vnode.state.filterList.map((f, idx) => { + try { + return filterParser.parse(f); + } catch (err) { + vnode.state.filterInvalid |= 1 << idx; + } + }); + vnode.state.filterList.push(""); + if (!vnode.state.filterInvalid) + vnode.attrs.onChange(["AND"].concat(filter)); + } return m( "div.filter", [m("b", "Filter"), a].concat( - filterList.map((fltr, idx) => { + vnode.state.filterList.map((fltr, idx) => { return m("input", { type: "string", list: "filters", + class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`, value: fltr, - onchange: function(e) { - e.redraw = false; - filterList[idx] = this.value; - m.route.set("/devices", { - filter: filterList.filter(f => !!f) - }); + onchange: e => { + vnode.state.filterList[idx] = e.target.value; + onChange(); } }); })