diff --git a/client/components/parameter-list.js b/client/components/parameter-list.js index d0e1af5..9636851 100644 --- a/client/components/parameter-list.js +++ b/client/components/parameter-list.js @@ -2,27 +2,41 @@ import m from "mithril"; import * as components from "../components"; +import * as store from "../store"; +import * as filterParser from "../../common/filter-parser"; const component = { + oninit: vnode => { + vnode.state.parameters = Object.values(vnode.attrs.parameters).map( + parameter => + Object.assign({}, parameter, { + parameter: filterParser.parseParameter(parameter.parameter) + }) + ); + }, view: vnode => { const device = vnode.attrs.device; - const parameters = vnode.attrs.parameters; - const filtered = Object.values(parameters).filter( - p => p.parameter in device - ); - const rows = filtered.map(p => - m( - "tr", - m("th", p.label), + + const rows = []; + for (let parameter of vnode.state.parameters) { + let p = store.evaluateExpression(parameter.parameter, device); + + if (!(p in device)) continue; + rows.push( m( - "td", + "tr", + m("th", parameter.label), m( - components.get(p.type || "parameter"), - Object.assign({ device: device }, p) + "td", + m( + components.get(parameter.type || "parameter"), + Object.assign({}, parameter, { device: device, parameter: p }) + ) ) ) - ) - ); + ); + } + return m("table.parameter-list", rows); } }; diff --git a/client/components/parameter-table.js b/client/components/parameter-table.js index b11fb29..f3d1d4f 100644 --- a/client/components/parameter-table.js +++ b/client/components/parameter-table.js @@ -3,12 +3,23 @@ import m from "mithril"; import * as components from "../components"; import * as taskQueue from "../task-queue"; +import * as store from "../store"; +import * as filterParser from "../../common/filter-parser"; const component = { + oninit: vnode => { + vnode.state.object = filterParser.parseParameter(vnode.attrs.object); + vnode.state.parameters = Object.values(vnode.attrs.parameters).map( + parameter => { + let p = filterParser.parseParameter(parameter.parameter); + return Object.assign({}, parameter, { parameter: p }); + } + ); + }, view: vnode => { const device = vnode.attrs.device; - const object = vnode.attrs.object; - const parameters = vnode.attrs.parameters; + const object = store.evaluateExpression(vnode.state.object, device); + const parameters = vnode.state.parameters; if (!device[object]) return null; @@ -27,17 +38,19 @@ const component = { const rows = []; for (let i of instances) { - const row = Object.values(parameters).map(p => + const row = parameters.map(p => m( "td", m( components.get("parameter"), - Object.assign({ device: device }, p, { - parameter: `${i}.${p.parameter}` + Object.assign({}, p, { + device: device, + parameter: `${i}.${store.evaluateExpression(p.parameter, device)}` }) ) ) ); + if (device[i].writable === true) row.push( m( @@ -90,7 +103,11 @@ const component = { ) ) ); - return m("table.table", thead, m("tbody", rows)); + + let label; + if (vnode.attrs.label) label = m("h2", vnode.attrs.label); + + return [label, m("table.table", thead, m("tbody", rows))]; } }; diff --git a/client/components/parameter.js b/client/components/parameter.js index 7a8e520..0e75687 100644 --- a/client/components/parameter.js +++ b/client/components/parameter.js @@ -5,7 +5,9 @@ import * as taskQueue from "../task-queue"; const component = { view: vnode => { - const param = vnode.attrs.device[vnode.attrs.parameter]; + const device = vnode.attrs.device; + const parameter = vnode.attrs.parameter; + const param = device[parameter]; if (!param || !param.value) return m("span.na", "N/A"); let value = param.value[0]; if (param.value[1] === "xsd:dateTime") @@ -20,10 +22,8 @@ const component = { onclick: () => { taskQueue.stageSpv({ name: "setParameterValues", - device: vnode.attrs.device["DeviceID.ID"].value[0], - parameterValues: [ - [vnode.attrs.parameter, param.value[0], param.value[1]] - ] + device: device["DeviceID.ID"].value[0], + parameterValues: [[parameter, param.value[0], param.value[1]]] }); } }, diff --git a/client/components/summon-button.js b/client/components/summon-button.js index e814e20..81c2f7f 100644 --- a/client/components/summon-button.js +++ b/client/components/summon-button.js @@ -4,20 +4,30 @@ import m from "mithril"; import * as taskQueue from "../task-queue"; import * as store from "../store"; import * as notifications from "../notifications"; +import * as filterParser from "../../common/filter-parser"; const component = { + oninit: vnode => { + vnode.state.parameters = Object.values(vnode.attrs.parameters).map( + parameter => filterParser.parseParameter(parameter) + ); + }, view: vnode => { + const device = vnode.attrs.device; + return m( "button.primary", { title: "Initiate session and refresh basic parameters", onclick: e => { e.target.disabled = true; - const params = Object.values(vnode.attrs.parameters); + const params = vnode.state.parameters.map(p => + store.evaluateExpression(p, device) + ); const task = { name: "getParameterValues", parameterNames: params, - device: vnode.attrs.device["DeviceID.ID"].value[0] + device: device["DeviceID.ID"].value[0] }; taskQueue diff --git a/client/devices-page.js b/client/devices-page.js index 354d69d..0bf2109 100644 --- a/client/devices-page.js +++ b/client/devices-page.js @@ -12,7 +12,12 @@ import * as components from "./components"; function init(args) { return new Promise(resolve => { let filter = new Filter(args.filter); - resolve({ filter: filter }); + let indexParameters = Object.values(config.get("ui.index")).map(p => + Object.assign({}, p, { + parameter: filterParser.parseParameter(p.parameter) + }) + ); + resolve({ filter: filter, indexParameters: indexParameters }); }); } @@ -65,7 +70,10 @@ function renderTable( }, m("td", checkbox), parameters.map(p => { - const attrs = Object.assign({ device: device }, p); + const attrs = Object.assign({}, p, { + device: device, + parameter: store.evaluateExpression(p.parameter, device) + }); const comp = m(components.get(attrs.type || "parameter"), attrs); return m("td", comp); }), @@ -156,7 +164,7 @@ const component = { }), renderTable( devs, - Object.values(config.get("ui.index")), + vnode.attrs.indexParameters, count.value, selected, showMore diff --git a/client/store.js b/client/store.js index 76333c5..c9adbf1 100644 --- a/client/store.js +++ b/client/store.js @@ -3,10 +3,12 @@ import m from "mithril"; import Filter from "../common/filter"; import * as config from "./config"; +import * as filterParser from "../common/filter-parser"; let fulfillTimestamp = 0; -let unpackedFilters = new WeakMap(); +let unpackedFiltersCache = new WeakMap(); +let expressionsCache = new WeakMap(); const queries = { filter: new WeakMap(), @@ -45,10 +47,10 @@ class QueryResponse { } function unpackFilter(filter) { - let f = unpackedFilters.get(filter); + let f = unpackedFiltersCache.get(filter); if (!f) { f = f.evaluateExpressions(fulfillTimestamp); - unpackedFilters.set(filter, f); + unpackedFiltersCache.set(filter, f); } return f; } @@ -118,7 +120,9 @@ function fulfill(accessTimestamp, _fulfillTimestamp) { let updated = false; if (_fulfillTimestamp > fulfillTimestamp) { - unpackedFilters = new WeakMap(); + unpackedFiltersCache = new WeakMap(); + expressionsCache = new WeakMap(); + for (let resource of Object.values(resources)) resource.combinedFilter = null; fulfillTimestamp = _fulfillTimestamp; @@ -366,6 +370,22 @@ function deleteResource(resourceType, id) { }); } +function evaluateExpression(exp, device) { + if (!Array.isArray(exp)) return exp; + + let exps = expressionsCache.get(exp); + if (!exps) expressionsCache.set(exp, (exps = new WeakMap())); + + if (!exps.has(device)) { + let v = filterParser.evaluateExpressions(exp, e => { + if (e[0] === "FUNC" && e[1] === "NOW") return fulfillTimestamp; + }); + exps.set(device, v); + } + + return exps.get(device); +} + export { count, fetch, @@ -373,5 +393,6 @@ export { unpackFilter, getTimestamp, postTasks, - deleteResource + deleteResource, + evaluateExpression }; diff --git a/common/filter-parser.js b/common/filter-parser.js index 7be373e..3063de3 100644 --- a/common/filter-parser.js +++ b/common/filter-parser.js @@ -205,6 +205,36 @@ function parseExpression(exp) { return lang.ValueExpression.tryParse(exp); } +function evaluateExpressions(ast, funcCallback) { + return map(ast, exp => { + if (exp[0] === "FUNC") { + return funcCallback(exp); + } else if (exp[0] === "*") { + let v = exp[1]; + for (let i = 2; i < exp.length; ++i) v *= exp[i]; + return v; + } else if (exp[0] === "/") { + let v = exp[1]; + for (let i = 2; i < exp.length; ++i) v /= exp[i]; + return v; + } else if (exp[0] === "+") { + let v = exp[1]; + for (let i = 2; i < exp.length; ++i) v += exp[i]; + return v; + } else if (exp[0] === "-") { + let v = exp[1]; + for (let i = 2; i < exp.length; ++i) v -= exp[i]; + return v; + } else if (exp[0] === "||") { + return exp.slice(1).join(""); + } + }); +} + +function parseParameter(p) { + return lang.Parameter.tryParse(p); +} + function stringify(filter) { function value(v) { if (Array.isArray(v)) @@ -275,5 +305,7 @@ function map(filter, callback) { exports.parse = parse; exports.parseExpression = parseExpression; +exports.evaluateExpressions = evaluateExpressions; +exports.parseParameter = parseParameter; exports.stringify = stringify; exports.map = map; diff --git a/common/filter.js b/common/filter.js index c4afc6c..4a25086 100644 --- a/common/filter.js +++ b/common/filter.js @@ -67,28 +67,9 @@ class Filter { evaluateExpressions(now = Date.now()) { if (!this.ast) return this; - let ast = filterParser.map(this.ast, exp => { - if (exp[0] === "FUNC" && exp[1] === "NOW") { - return now; - } else if (exp[0] === "*") { - let v = exp[1]; - for (let i = 2; i < exp.length; ++i) v *= exp[i]; - return v; - } else if (exp[0] === "/") { - let v = exp[1]; - for (let i = 2; i < exp.length; ++i) v /= exp[i]; - return v; - } else if (exp[0] === "+") { - let v = exp[1]; - for (let i = 2; i < exp.length; ++i) v += exp[i]; - return v; - } else if (exp[0] === "-") { - let v = exp[1]; - for (let i = 2; i < exp.length; ++i) v -= exp[i]; - return v; - } else if (exp[0] === "||") { - return exp.slice(1).join(""); - } + + let ast = filterParser.evaluateExpressions(this.ast, exp => { + if (exp[0] === "FUNC" && exp[1] === "NOW") return now; }); if (ast === this.ast) return this; diff --git a/config.json b/config.json index 780bb8d..c0a7db6 100644 --- a/config.json +++ b/config.json @@ -85,6 +85,7 @@ "ui.device.1.parameters.11.label": "WLAN passphrase", "ui.device.1.parameters.11.parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", "ui.device.2.type": "parameter-table", + "ui.device.2.label": "WLAN hosts", "ui.device.2.object": "InternetGatewayDevice.LANDevice.1.Hosts.Host", "ui.device.2.parameters.1.label": "Host name", "ui.device.2.parameters.1.parameter": "HostName", diff --git a/server/device.js b/server/device.js index 99470f2..28da8d0 100644 --- a/server/device.js +++ b/server/device.js @@ -109,9 +109,9 @@ function recursive(input, root, output, timestamp) { childrenTimestamp = input["_timestamp"]; const attrs = {}; - if (tree["_value"]) { + if (tree["_value"] != null) { attrs["value"] = [tree["_value"], tree["_type"]]; - attrs["valueTimestamp"] = childrenTimestamp; + attrs["valueTimestamp"] = tree["_timestamp"] || childrenTimestamp; attrs["object"] = false; attrs["objectTimestamp"] = childrenTimestamp; } else if (tree["_object"] != null) {