Add linting rules

This commit is contained in:
Zaid Abdulla committed 2018-12-27 01:21:16 -08:00
1 parent 75a5a42296
commit c702003c32
43 files changed
+705 -576

No files matched your search

+4 -1
View File
@@ -10,6 +10,9 @@
"no-shadow": ["error", { "allow": ["err"] }], "no-shadow": ["error", { "allow": ["err"] }],
"handle-callback-err": "error", "handle-callback-err": "error",
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"curly": ["error", "multi", "consistent"] "curly": ["error", "multi-or-nest", "consistent"],
"consistent-return": ["error", {"treatUndefinedAsUnspecified": true}],
"no-buffer-constructor": "error",
"prefer-const": ["error", {"destructuring": "all"}]
} }
} }
+2 -2
View File
@@ -51,14 +51,14 @@ function pagify(pageName, page) {
if (!page.init) { if (!page.init) {
state = null; state = null;
fulfill(); fulfill();
return; return null;
} }
return new Promise(resolve => { return new Promise(resolve => {
page page
.init(args) .init(args)
.then(st => { .then(st => {
if (!st) return m.route.set("/"); if (!st) return void m.route.set("/");
state = st; state = st;
resolve(); resolve();
fulfill(); fulfill();
+8 -6
View File
@@ -20,7 +20,7 @@ class Autocomplete {
el.addEventListener("focus", () => { el.addEventListener("focus", () => {
this.element = el; this.element = el;
let domRect = el.getBoundingClientRect(); const domRect = el.getBoundingClientRect();
this.container.style.left = `${domRect.left}px`; this.container.style.left = `${domRect.left}px`;
this.container.style.width = `${domRect.width}px`; this.container.style.width = `${domRect.width}px`;
this.container.style.top = `${domRect.bottom}px`; this.container.style.top = `${domRect.bottom}px`;
@@ -36,13 +36,15 @@ class Autocomplete {
el.addEventListener("keydown", e => { el.addEventListener("keydown", e => {
if (this.element !== el) return; if (this.element !== el) return;
if (e.key === "Escape") this.hide(); if (e.key === "Escape") {
else if (e.key === "Enter") this.hide();
} else if (e.key === "Enter") {
if (this.default != null) { if (this.default != null) {
el.value = this.default; el.value = this.default;
e.preventDefault(); e.preventDefault();
this.update(); this.update();
} }
}
}); });
el.addEventListener("input", () => { el.addEventListener("input", () => {
@@ -94,10 +96,10 @@ class Autocomplete {
this.default = suggestions[0]; this.default = suggestions[0];
for (let suggestion of suggestions) { for (const suggestion of suggestions) {
let e = document.createElement("div"); const e = document.createElement("div");
e.className = "suggestion"; e.className = "suggestion";
let t = document.createTextNode(suggestion); const t = document.createTextNode(suggestion);
e.appendChild(t); e.appendChild(t);
e.addEventListener("mousedown", ev => { e.addEventListener("mousedown", ev => {
ev.preventDefault(); ev.preventDefault();
+10 -9
View File
@@ -12,7 +12,7 @@ const component = {
view: vnode => { view: vnode => {
const device = vnode.attrs.device; const device = vnode.attrs.device;
let search = m("input", { const search = m("input", {
type: "text", type: "text",
placeholder: "Search parameters", placeholder: "Search parameters",
oninput: e => { oninput: e => {
@@ -20,10 +20,10 @@ const component = {
} }
}); });
let instanceRegex = /\.[0-9]+$/; const instanceRegex = /\.[0-9]+$/;
let re; let re;
if (vnode.state.searchString) { if (vnode.state.searchString) {
let keywords = vnode.state.searchString.split(" ").filter(s => s); const keywords = vnode.state.searchString.split(" ").filter(s => s);
if (keywords.length) if (keywords.length)
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i"); re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
} }
@@ -31,22 +31,22 @@ const component = {
const rows = Object.keys(device) const rows = Object.keys(device)
.sort() .sort()
.map(k => { .map(k => {
let p = device[k]; const p = device[k];
let val = []; const val = [];
let attrs = {}; const attrs = {};
if (re) { if (re) {
let str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k; const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
if (!re.test(str)) attrs.style = "display: none;"; if (!re.test(str)) attrs.style = "display: none;";
} }
if (p.object === false) if (p.object === false) {
val.push( val.push(
m( m(
components.get("parameter"), components.get("parameter"),
Object.assign({ device: device, parameter: k }) Object.assign({ device: device, parameter: k })
) )
); );
else if (p.object && p.writable) } else if (p.object && p.writable) {
if (instanceRegex.test(k)) { if (instanceRegex.test(k)) {
val.push( val.push(
m( m(
@@ -82,6 +82,7 @@ const component = {
) )
); );
} }
}
val.push( val.push(
m( m(
+2 -2
View File
@@ -12,10 +12,10 @@ const component = {
view: vnode => { view: vnode => {
if (vnode.attrs.filter) { if (vnode.attrs.filter) {
const filter = memoizedParse(vnode.attrs.filter); const filter = memoizedParse(vnode.attrs.filter);
if (!store.evaluateExpression(filter, vnode.attrs.device)) return; if (!store.evaluateExpression(filter, vnode.attrs.device)) return null;
} }
let children = Object.values(vnode.attrs.components).map(c => { const children = Object.values(vnode.attrs.components).map(c => {
if (typeof c !== "object") return `${c}`; if (typeof c !== "object") return `${c}`;
const attrs = Object.assign({}, vnode.attrs, c); const attrs = Object.assign({}, vnode.attrs, c);
return m(components.get(attrs.type), attrs); return m(components.get(attrs.type), attrs);
+6 -3
View File
@@ -20,7 +20,7 @@ const component = {
const thead = m("thead", m("tr", headers)); const thead = m("thead", m("tr", headers));
const rows = []; const rows = [];
for (let f of faults.value) for (const f of faults.value) {
rows.push([ rows.push([
m("td", f["channel"]), m("td", f["channel"]),
m("td", f["code"]), m("td", f["code"]),
@@ -51,14 +51,17 @@ const component = {
) )
) )
]); ]);
}
let tbody; let tbody;
if (rows.length) tbody = m("tbody", rows.map(r => m("tr", r))); if (rows.length) {
else tbody = m("tbody", rows.map(r => m("tr", r)));
} else {
tbody = m( tbody = m(
"tbody", "tbody",
m("tr.empty", m("td", { colspan: headers.length }, "No faults")) m("tr.empty", m("td", { colspan: headers.length }, "No faults"))
); );
}
return m("table.table", thead, tbody); return m("table.table", thead, tbody);
} }
+2 -1
View File
@@ -14,7 +14,7 @@ const component = {
view: vnode => { view: vnode => {
const device = vnode.attrs.device; const device = vnode.attrs.device;
const chart = CHARTS[vnode.attrs.chart]; const chart = CHARTS[vnode.attrs.chart];
for (let slice of Object.values(chart.slices)) { for (const slice of Object.values(chart.slices)) {
const filter = memoizedParse(slice.filter); const filter = memoizedParse(slice.filter);
if (store.evaluateExpression(filter, device)) { if (store.evaluateExpression(filter, device)) {
const dot = m( const dot = m(
@@ -35,6 +35,7 @@ const component = {
return m("span.overview-dot", dot, `${slice.label}`); return m("span.overview-dot", dot, `${slice.label}`);
} }
} }
return null;
} }
}; };
+11 -7
View File
@@ -9,7 +9,7 @@ import * as expressionParser from "../../common/expression-parser";
const component = { const component = {
oninit: vnode => { oninit: vnode => {
let obj = expression.parse(vnode.attrs.parameter); const obj = expression.parse(vnode.attrs.parameter);
if (!Array.isArray(obj) || !obj[0] === "PARAM") if (!Array.isArray(obj) || !obj[0] === "PARAM")
throw new Error("Object must be a parameter path"); throw new Error("Object must be a parameter path");
vnode.state.object = obj[1]; vnode.state.object = obj[1];
@@ -30,19 +30,20 @@ const component = {
const instances = new Set(); const instances = new Set();
const prefix = `${object}.`; const prefix = `${object}.`;
for (let p in device) for (const p in device) {
if (p.startsWith(prefix)) { if (p.startsWith(prefix)) {
let i = p.indexOf(".", prefix.length); const i = p.indexOf(".", prefix.length);
if (i === -1) instances.add(p); if (i === -1) instances.add(p);
else instances.add(p.slice(0, i)); else instances.add(p.slice(0, i));
} }
}
const headers = Object.values(parameters).map(p => m("th", p.label)); const headers = Object.values(parameters).map(p => m("th", p.label));
const thead = m("thead", m("tr", headers)); const thead = m("thead", m("tr", headers));
const rows = []; const rows = [];
for (let i of instances) { for (const i of instances) {
const row = parameters.map(p => { const row = parameters.map(p => {
const param = expressionParser.map(p.parameter, e => { const param = expressionParser.map(p.parameter, e => {
if (Array.isArray(e) && e[0] === "PARAM") if (Array.isArray(e) && e[0] === "PARAM")
@@ -62,7 +63,7 @@ const component = {
); );
}); });
if (device[i].writable === true) if (device[i].writable === true) {
row.push( row.push(
m( m(
"td", "td",
@@ -82,15 +83,17 @@ const component = {
) )
) )
); );
}
rows.push(m("tr", row)); rows.push(m("tr", row));
} }
if (!rows.length) if (!rows.length) {
rows.push( rows.push(
m("tr.empty", m("td", { colspan: headers.length }, "No instances")) m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
); );
}
if (device[object].writable === true) if (device[object].writable === true) {
rows.push( rows.push(
m( m(
"tr", "tr",
@@ -114,6 +117,7 @@ const component = {
) )
) )
); );
}
let label; let label;
if (vnode.attrs.label) label = m("h2", vnode.attrs.label); if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
+16 -20
View File
@@ -11,27 +11,22 @@ const memoizedParse = memoize(expression.parse);
const evaluateParam = memoize((exp, obj, now) => { const evaluateParam = memoize((exp, obj, now) => {
let timestamp = now; let timestamp = now;
let params = new Set(); const params = new Set();
let value = expression.evaluate( const value = expression.evaluate(memoizedParse(exp), obj, now, e => {
memoizedParse(exp), if (Array.isArray(e)) {
obj, if (e[0] === "PARAM") {
now, params.add(e[1]);
e => { if (!Array.isArray(e[1])) {
if (Array.isArray(e)) { const p = obj[e[1]];
if (e[0] === "PARAM") { if (p && p.valueTimestamp)
params.add(e[1]); timestamp = Math.min(timestamp, p.valueTimestamp);
if (!Array.isArray(e[1])) {
let p = obj[e[1]];
if (p && p.valueTimestamp)
timestamp = Math.min(timestamp, p.valueTimestamp);
}
} }
if (e[0] === "FUNC" && e[1] === "DATE_STRING" && !Array.isArray(e[2]))
return new Date(e[2]).toLocaleString();
} }
return e; if (e[0] === "FUNC" && e[1] === "DATE_STRING" && !Array.isArray(e[2]))
return new Date(e[2]).toLocaleString();
} }
); return e;
});
let parameter = params.size === 1 ? params.values().next().value : null; let parameter = params.size === 1 ? params.values().next().value : null;
if (Array.isArray(parameter)) parameter = null; if (Array.isArray(parameter)) parameter = null;
@@ -42,7 +37,7 @@ const component = {
view: vnode => { view: vnode => {
const device = vnode.attrs.device; const device = vnode.attrs.device;
let { value, timestamp, parameter } = evaluateParam( const { value, timestamp, parameter } = evaluateParam(
vnode.attrs.parameter, vnode.attrs.parameter,
vnode.attrs.device, vnode.attrs.device,
store.getTimestamp() store.getTimestamp()
@@ -51,7 +46,7 @@ const component = {
if (value == null) return null; if (value == null) return null;
let edit; let edit;
if (device[parameter] && device[parameter].writable) if (device[parameter] && device[parameter].writable) {
edit = m( edit = m(
"button", "button",
{ {
@@ -72,6 +67,7 @@ const component = {
}, },
"✎" "✎"
); );
}
return m( return m(
"span.parameter-value", "span.parameter-value",
+5 -3
View File
@@ -13,7 +13,7 @@ const component = {
vnode.state.timeout = null; vnode.state.timeout = null;
}, },
view: vnode => { view: vnode => {
let refresh = () => { const refresh = () => {
const device = vnode.attrs.device; const device = vnode.attrs.device;
let param = let param =
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]; device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
@@ -34,20 +34,22 @@ const component = {
}); });
}; };
let t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now(); const t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now();
clearTimeout(vnode.state.timeout); clearTimeout(vnode.state.timeout);
if (t <= 0) refresh(); if (t <= 0) refresh();
else vnode.state.timeout = setTimeout(refresh, t); else vnode.state.timeout = setTimeout(refresh, t);
if (vnode.state.host) { if (vnode.state.host) {
let status = ""; let status = "";
if (vnode.state.ping) if (vnode.state.ping) {
if (vnode.state.ping.avg != null) if (vnode.state.ping.avg != null)
status = `${Math.trunc(vnode.state.ping.avg)} ms`; status = `${Math.trunc(vnode.state.ping.avg)} ms`;
else status = "Unreachable"; else status = "Unreachable";
}
return m("div", `Pinging ${vnode.state.host}: ${status}`); return m("div", `Pinging ${vnode.state.host}: ${status}`);
} }
return null;
} }
}; };
+17 -15
View File
@@ -12,8 +12,8 @@ const singular = {
function createField(current, attr, focus) { function createField(current, attr, focus) {
if (attr.type === "combo") { if (attr.type === "combo") {
let options = [m("option", { value: "" }, "--Please choose--")]; const options = [m("option", { value: "" }, "--Please choose--")];
for (let op of attr.options) options.push(m("option", { value: op }, op)); for (const op of attr.options) options.push(m("option", { value: op }, op));
let selected = ""; let selected = "";
if (attr.options.includes(current.object[attr.id])) if (attr.options.includes(current.object[attr.id]))
@@ -40,7 +40,7 @@ function createField(current, attr, focus) {
name: attr.id, name: attr.id,
value: current.object[attr.id], value: current.object[attr.id],
oncreate: _vnode => { oncreate: _vnode => {
let editor = codeMirror.fromTextArea(_vnode.dom, { const editor = codeMirror.fromTextArea(_vnode.dom, {
mode: "javascript", mode: "javascript",
lineNumbers: true lineNumbers: true
}); });
@@ -87,21 +87,22 @@ function createField(current, attr, focus) {
const component = { const component = {
view: vnode => { view: vnode => {
let actionHandler = vnode.attrs.actionHandler; const actionHandler = vnode.attrs.actionHandler;
let attributes = vnode.attrs.attributes; const attributes = vnode.attrs.attributes;
let resource = vnode.attrs.resource; const resource = vnode.attrs.resource;
let base = vnode.attrs.base || {}; const base = vnode.attrs.base || {};
if (!vnode.state.current) if (!vnode.state.current) {
vnode.state.current = { vnode.state.current = {
isNew: !base["_id"], isNew: !base["_id"],
object: Object.assign({}, base) object: Object.assign({}, base)
}; };
}
let current = vnode.state.current; const current = vnode.state.current;
let form = []; const form = [];
let focused = false; let focused = false;
for (let attr of attributes) { for (const attr of attributes) {
let focus = false; let focus = false;
if (!focused && (current.isNew || attr.id !== "_id")) if (!focused && (current.isNew || attr.id !== "_id"))
focus = focused = true; focus = focused = true;
@@ -117,9 +118,9 @@ const component = {
} }
const submit = m("button.primary", { type: "submit" }, "Save"); const submit = m("button.primary", { type: "submit" }, "Save");
let buttons = [submit]; const buttons = [submit];
if (!current.isNew) if (!current.isNew) {
buttons.push( buttons.push(
m( m(
"button.primary", "button.primary",
@@ -135,10 +136,11 @@ const component = {
"Delete" "Delete"
) )
); );
}
form.push(m(".actions-bar", buttons)); form.push(m(".actions-bar", buttons));
let children = [ const children = [
m("h1", `${current.isNew ? "New" : "Editing"} ${singular[resource]}`), m("h1", `${current.isNew ? "New" : "Editing"} ${singular[resource]}`),
m( m(
"form", "form",
@@ -147,7 +149,7 @@ const component = {
e.redraw = false; e.redraw = false;
e.target.onsubmit = null; e.target.onsubmit = null;
e.preventDefault(); e.preventDefault();
for (let elem of e.target.elements) elem.disabled = true; for (const elem of e.target.elements) elem.disabled = true;
submit.dom.textContent = "Loading ..."; submit.dom.textContent = "Loading ...";
actionHandler("save", current.object); actionHandler("save", current.object);
} }
+11 -10
View File
@@ -37,28 +37,29 @@ const component = {
.commit( .commit(
[task], [task],
(deviceId, err, connectionRequestStatus, tasks2) => { (deviceId, err, connectionRequestStatus, tasks2) => {
if (err) if (err) {
return notifications.push( notifications.push("error", `${deviceId}: ${err.message}`);
"error", return;
`${deviceId}: ${err.message}` }
);
for (let t of tasks2) for (const t of tasks2)
if (t.status === "stale") taskQueue.deleteTask(t); if (t.status === "stale") taskQueue.deleteTask(t);
if (connectionRequestStatus !== "OK") if (connectionRequestStatus !== "OK") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${connectionRequestStatus}` `${deviceId}: ${connectionRequestStatus}`
); );
else if (tasks2[0].status === "stale") } else if (tasks2[0].status === "stale") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: No contact from device` `${deviceId}: No contact from device`
); );
else if (tasks2[0].status === "fault") } else if (tasks2[0].status === "fault") {
notifications.push("error", `${deviceId}: Refresh faulted`); notifications.push("error", `${deviceId}: Refresh faulted`);
else notifications.push("success", `${deviceId}: Summoned`); } else {
notifications.push("success", `${deviceId}: Summoned`);
}
} }
) )
.then(() => { .then(() => {
+1 -1
View File
@@ -11,7 +11,7 @@ const component = {
if (vnode.attrs.writable != null) writable = vnode.attrs.writable; if (vnode.attrs.writable != null) writable = vnode.attrs.writable;
const tags = []; const tags = [];
for (let p of Object.keys(device)) for (const p of Object.keys(device))
if (p.startsWith("Tags.")) tags.push(p.slice(5)); if (p.startsWith("Tags.")) tags.push(p.slice(5));
tags.sort(); tags.sort();
+5 -3
View File
@@ -7,10 +7,11 @@ import * as store from "./store";
import * as components from "./components"; import * as components from "./components";
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("devices", 2)) if (!window.authorizer.hasAccess("devices", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
return Promise.resolve({ return Promise.resolve({
deviceId: args.id, deviceId: args.id,
@@ -22,15 +23,16 @@ const component = {
view: vnode => { view: vnode => {
document.title = `${vnode.attrs.deviceId} - Devices - GenieACS`; document.title = `${vnode.attrs.deviceId} - Devices - GenieACS`;
let dev = store.fetch("devices", vnode.attrs.deviceFilter).value; const dev = store.fetch("devices", vnode.attrs.deviceFilter).value;
if (!dev.length) return "Loading"; if (!dev.length) return "Loading";
const conf = config.ui.device; const conf = config.ui.device;
const cmps = []; const cmps = [];
for (let c of Object.values(conf)) for (const c of Object.values(conf)) {
cmps.push( cmps.push(
m(components.get(c["type"]), Object.assign({ device: dev[0] }, c)) m(components.get(c["type"]), Object.assign({ device: dev[0] }, c))
); );
}
return [ return [
m("h1", vnode.attrs.deviceId), m("h1", vnode.attrs.deviceId),
+34 -29
View File
@@ -18,11 +18,9 @@ const PAGE_SIZE = config.ui.pageSize || 10;
const memoizedParse = memoize(expression.parse); const memoizedParse = memoize(expression.parse);
const memoizedJsonParse = memoize(JSON.parse); const memoizedJsonParse = memoize(JSON.parse);
const memoizedGetSortable = memoize(str => { const memoizedGetSortable = memoize(str => {
let expressionParams = expression.extractParams( const expressionParams = expression.extractParams(expression.parse(str));
expression.parse(str)
);
if (expressionParams.length === 1) { if (expressionParams.length === 1) {
let param = expression.evaluate(expressionParams[0]); const param = expression.evaluate(expressionParams[0]);
if (typeof param === "string") return param; if (typeof param === "string") return param;
} }
return null; return null;
@@ -52,7 +50,7 @@ const unpackSmartQuery = memoize(query => {
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))
return reject(new Error("You are not authorized to view this page")); return void reject(new Error("You are not authorized to view this page"));
const filter = args.filter; const filter = args.filter;
const sort = args.sort; const sort = args.sort;
@@ -76,16 +74,17 @@ function renderTable(
type: "checkbox", type: "checkbox",
checked: devices.length && selected.size === devices.length, checked: devices.length && selected.size === devices.length,
onchange: e => { onchange: e => {
for (let d of devices) for (const d of devices) {
if (e.target.checked) selected.add(d["DeviceID.ID"].value[0]); if (e.target.checked) selected.add(d["DeviceID.ID"].value[0]);
else selected.delete(d["DeviceID.ID"].value[0]); else selected.delete(d["DeviceID.ID"].value[0]);
}
}, },
disabled: !total disabled: !total
}); });
let labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let param of parameters) { for (const param of parameters) {
let label = param.label; const label = param.label;
let _param; let _param;
if (!param.unsortable && (_param = memoizedGetSortable(param.parameter))) { if (!param.unsortable && (_param = memoizedGetSortable(param.parameter))) {
let direction = 1; let direction = 1;
@@ -94,7 +93,7 @@ function renderTable(
if (sort[_param] > 0) symbol = "\u2bc6"; if (sort[_param] > 0) symbol = "\u2bc6";
else if (sort[_param] < 0) symbol = "\u2bc5"; else if (sort[_param] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -111,9 +110,9 @@ function renderTable(
} }
} }
let rows = []; const rows = [];
for (let device of devices) { for (const device of devices) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(device["DeviceID.ID"].value[0]), checked: selected.has(device["DeviceID.ID"].value[0]),
onchange: e => { onchange: e => {
@@ -158,12 +157,13 @@ function renderTable(
); );
} }
if (!rows.length) if (!rows.length) {
rows.push( rows.push(
m("tr.empty", m("td", { colspan: parameters.length + 1 }, "No devices")) m("tr.empty", m("td", { colspan: parameters.length + 1 }, "No devices"))
); );
}
let footerElements = []; const footerElements = [];
if (total != null) footerElements.push(`${devices.length}/${total}`); if (total != null) footerElements.push(`${devices.length}/${total}`);
else footerElements.push(`${devices.length}`); else footerElements.push(`${devices.length}`);
@@ -179,12 +179,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -206,11 +207,12 @@ function renderActions(selected) {
title: "Reboot selected devices", title: "Reboot selected devices",
disabled: !selected.size, disabled: !selected.size,
onclick: () => { onclick: () => {
for (let d of selected) for (const d of selected) {
taskQueue.queueTask({ taskQueue.queueTask({
name: "reboot", name: "reboot",
device: d device: d
}); });
}
} }
}, },
"Reboot" "Reboot"
@@ -224,11 +226,12 @@ function renderActions(selected) {
title: "Factory reset selected devices", title: "Factory reset selected devices",
disabled: !selected.size, disabled: !selected.size,
onclick: () => { onclick: () => {
for (let d of selected) for (const d of selected) {
taskQueue.queueTask({ taskQueue.queueTask({
name: "factoryReset", name: "factoryReset",
device: d device: d
}); });
}
} }
}, },
"Reset" "Reset"
@@ -246,7 +249,7 @@ function renderActions(selected) {
if (!confirm(`Deleting ${ids.length} devices. Are you sure?`)) return; if (!confirm(`Deleting ${ids.length} devices. Are you sure?`)) return;
let counter = 1; let counter = 1;
for (let id of ids) { for (const id of ids) {
++counter; ++counter;
store store
.deleteResource("devices", id) .deleteResource("devices", id)
@@ -278,7 +281,7 @@ function renderActions(selected) {
if (!tag) return; if (!tag) return;
let counter = 1; let counter = 1;
for (let id of ids) { for (const id of ids) {
++counter; ++counter;
store store
.updateTags(id, { [tag]: true }) .updateTags(id, { [tag]: true })
@@ -312,7 +315,7 @@ function renderActions(selected) {
if (!tag) return; if (!tag) return;
let counter = 1; let counter = 1;
for (let id of ids) { for (const id of ids) {
++counter; ++counter;
store store
.updateTags(id, { [tag]: false }) .updateTags(id, { [tag]: false })
@@ -345,13 +348,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/devices", ops); m.route.set("/devices", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/devices", ops); m.route.set("/devices", ops);
} }
@@ -360,17 +363,19 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let devs = store.fetch("devices", filter, { const devs = store.fetch("devices", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("devices", filter); const count = store.count("devices", filter);
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let d of devs.value) for (const d of devs.value) {
if (vnode.state.selected.has(d["DeviceID.ID"].value[0])) if (vnode.state.selected.has(d["DeviceID.ID"].value[0]))
selected.add(d["DeviceID.ID"].value[0]); selected.add(d["DeviceID.ID"].value[0]);
}
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl( const downloadUrl = getDownloadUrl(
+42 -34
View File
@@ -89,7 +89,7 @@ function renderStagingDownload(task) {
onclick: () => { onclick: () => {
task.fileName = f; task.fileName = f;
task.fileType = ""; task.fileType = "";
for (let file of files.value) for (const file of files.value)
if (file._id === f) task.fileType = file["metadata.fileType"]; if (file._id === f) task.fileType = file["metadata.fileType"];
} }
}, },
@@ -110,8 +110,8 @@ function renderStagingDownload(task) {
} }
function renderStaging(staging) { function renderStaging(staging) {
let elements = []; const elements = [];
for (let s of staging) { for (const s of staging) {
const queueFunc = () => { const queueFunc = () => {
staging.delete(s); staging.delete(s);
taskQueue.queueTask(s); taskQueue.queueTask(s);
@@ -143,19 +143,19 @@ function renderStaging(staging) {
} }
function renderQueue(queue) { function renderQueue(queue) {
let details = []; const details = [];
let devices = {}; const devices = {};
for (let t of queue) { for (const t of queue) {
devices[t.device] = devices[t.device] || []; devices[t.device] = devices[t.device] || [];
devices[t.device].push(t); devices[t.device].push(t);
} }
for (let [k, v] of Object.entries(devices)) { for (const [k, v] of Object.entries(devices)) {
details.push(m("strong", k)); details.push(m("strong", k));
for (let t of v) { for (const t of v) {
let actions = []; const actions = [];
if (t.status === "fault" || t.status === "stale") if (t.status === "fault" || t.status === "stale") {
actions.push( actions.push(
m( m(
"button", "button",
@@ -168,6 +168,7 @@ function renderQueue(queue) {
"↺" "↺"
) )
); );
}
actions.push( actions.push(
m( m(
@@ -182,7 +183,7 @@ function renderQueue(queue) {
) )
); );
if (t.name === "setParameterValues") if (t.name === "setParameterValues") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -197,7 +198,7 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else if (t.name === "refreshObject") } else if (t.name === "refreshObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -205,13 +206,13 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else if (t.name === "reboot") } else if (t.name === "reboot") {
details.push(m(`div.${t.status}`, "Reboot", m(".actions", actions))); details.push(m(`div.${t.status}`, "Reboot", m(".actions", actions)));
else if (t.name === "factoryReset") } else if (t.name === "factoryReset") {
details.push( details.push(
m(`div.${t.status}`, "Factory reset", m(".actions", actions)) m(`div.${t.status}`, "Factory reset", m(".actions", actions))
); );
else if (t.name === "addObject") } else if (t.name === "addObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -219,7 +220,7 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else if (t.name === "deleteObject") } else if (t.name === "deleteObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -227,7 +228,7 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else if (t.name === "getParameterValues") } else if (t.name === "getParameterValues") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -235,7 +236,7 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else if (t.name === "download") } else if (t.name === "download") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
@@ -243,7 +244,9 @@ function renderQueue(queue) {
m(".actions", actions) m(".actions", actions)
) )
); );
else details.push(m(`div.${t.status}`, t.name, m(".actions", actions))); } else {
details.push(m(`div.${t.status}`, t.name, m(".actions", actions)));
}
} }
} }
@@ -253,7 +256,7 @@ function renderQueue(queue) {
function renderNotifications(notifs) { function renderNotifications(notifs) {
const notificationElements = []; const notificationElements = [];
for (let n of notifs) for (const n of notifs) {
notificationElements.push( notificationElements.push(
m( m(
"div.notification", "div.notification",
@@ -276,6 +279,7 @@ function renderNotifications(notifs) {
n.message n.message
) )
); );
}
return notificationElements; return notificationElements;
} }
@@ -286,13 +290,13 @@ const component = {
const notifs = notifications.getNotifications(); const notifs = notifications.getNotifications();
let drawerElement, statusElement; let drawerElement, statusElement;
let notificationElements = renderNotifications(notifs); const notificationElements = renderNotifications(notifs);
let stagingElements = renderStaging(staging); const stagingElements = renderStaging(staging);
let queueElements = renderQueue(queue); const queueElements = renderQueue(queue);
function repositionNotifications() { function repositionNotifications() {
let top = 10; let top = 10;
for (let c of notificationElements) { for (const c of notificationElements) {
c.dom.style.top = top; c.dom.style.top = top;
top += c.dom.offsetHeight + 10; top += c.dom.offsetHeight + 10;
} }
@@ -300,20 +304,21 @@ const component = {
function resizeDrawer() { function resizeDrawer() {
let height = statusElement.dom.offsetTop + statusElement.dom.offsetHeight; let height = statusElement.dom.offsetTop + statusElement.dom.offsetHeight;
if (stagingElements.length) if (stagingElements.length) {
for (let s of stagingElements) for (const s of stagingElements)
height = Math.max(height, s.dom.offsetTop + s.dom.offsetHeight); height = Math.max(height, s.dom.offsetTop + s.dom.offsetHeight);
else if (vnode.state.mouseIn) } else if (vnode.state.mouseIn) {
for (let c of drawerElement.children) for (const c of drawerElement.children)
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight); height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
}
drawerElement.dom.style.height = height; drawerElement.dom.style.height = height;
} }
if (stagingElements.length + queueElements.length) { if (stagingElements.length + queueElements.length) {
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 }; const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
for (let t of queue) statusCount[t.status] += 1; for (const t of queue) statusCount[t.status] += 1;
let actions = m( const actions = m(
".actions", ".actions",
m( m(
"button.primary", "button.primary",
@@ -321,18 +326,20 @@ const component = {
title: "Commit queued tasks", title: "Commit queued tasks",
disabled: !statusCount.queued, disabled: !statusCount.queued,
onclick: () => { onclick: () => {
let tasks = Array.from(taskQueue.getQueue()).filter( const tasks = Array.from(taskQueue.getQueue()).filter(
t => t.status === "queued" t => t.status === "queued"
); );
taskQueue taskQueue
.commit( .commit(
tasks, tasks,
(deviceId, err, connectionRequestStatus, tasks2) => { (deviceId, err, connectionRequestStatus, tasks2) => {
if (err) if (err) {
return notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${err.message}` `${deviceId}: ${err.message}`
); );
return;
}
if (connectionRequestStatus !== "OK") { if (connectionRequestStatus !== "OK") {
notifications.push( notifications.push(
@@ -342,7 +349,7 @@ const component = {
return; return;
} }
for (let t of tasks2) for (const t of tasks2) {
if (t.status === "stale") { if (t.status === "stale") {
notifications.push( notifications.push(
"error", "error",
@@ -356,6 +363,7 @@ const component = {
); );
return; return;
} }
}
notifications.push( notifications.push(
"success", "success",
+28 -22
View File
@@ -24,10 +24,11 @@ const attributes = [
]; ];
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize(filter => {
let cols = {}; const cols = {};
for (let attr of attributes) for (const attr of attributes) {
cols[attr.label] = cols[attr.label] =
attr.id === "timestamp" ? `DATE_STRING(${attr.id})` : attr.id; attr.id === "timestamp" ? `DATE_STRING(${attr.id})` : attr.id;
}
return `/api/faults.csv?${m.buildQueryString({ return `/api/faults.csv?${m.buildQueryString({
filter: filter, filter: filter,
@@ -44,10 +45,11 @@ const unpackSmartQuery = memoize(query => {
}); });
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("faults", 2)) if (!window.authorizer.hasAccess("faults", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
const sort = args.sort; const sort = args.sort;
const filter = args.filter; const filter = args.filter;
@@ -68,16 +70,17 @@ function renderTable(
type: "checkbox", type: "checkbox",
checked: faults.length && selected.size === faults.length, checked: faults.length && selected.size === faults.length,
onchange: e => { onchange: e => {
for (let f of faults) for (const f of faults) {
if (e.target.checked) selected.add(f["_id"]); if (e.target.checked) selected.add(f["_id"]);
else selected.delete(f["_id"]); else selected.delete(f["_id"]);
}
}, },
disabled: !total disabled: !total
}); });
const labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let attr of attributes) { for (const attr of attributes) {
let label = attr.label; const label = attr.label;
let direction = 1; let direction = 1;
@@ -85,7 +88,7 @@ function renderTable(
if (sort[attr.id] > 0) symbol = "\u2bc6"; if (sort[attr.id] > 0) symbol = "\u2bc6";
else if (sort[attr.id] < 0) symbol = "\u2bc5"; else if (sort[attr.id] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -99,9 +102,9 @@ function renderTable(
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
} }
let rows = []; const rows = [];
for (let f of faults) { for (const f of faults) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(f["_id"]), checked: selected.has(f["_id"]),
onchange: e => { onchange: e => {
@@ -116,13 +119,14 @@ function renderTable(
const deviceHref = `#!/devices/${encodeURIComponent(f["device"])}`; const deviceHref = `#!/devices/${encodeURIComponent(f["device"])}`;
let tds = [m("td", checkbox)]; const tds = [m("td", checkbox)];
for (let attr of attributes) for (const attr of attributes) {
if (attr.id === "device") if (attr.id === "device")
tds.push(m("a", { href: deviceHref }, f[attr.id])); tds.push(m("a", { href: deviceHref }, f[attr.id]));
else if (attr.id === "timestamp") else if (attr.id === "timestamp")
tds.push(m("td", new Date(f[attr.id]).toLocaleString())); tds.push(m("td", new Date(f[attr.id]).toLocaleString()));
else tds.push(m("td", f[attr.id])); else tds.push(m("td", f[attr.id]));
}
rows.push( rows.push(
m( m(
@@ -145,7 +149,7 @@ function renderTable(
if (!rows.length) if (!rows.length)
rows.push(m("tr.empty", m("td", { colspan: 7 }, "No faults"))); rows.push(m("tr.empty", m("td", { colspan: 7 }, "No faults")));
let footerElements = []; const footerElements = [];
if (total != null) footerElements.push(`${faults.length}/${total}`); if (total != null) footerElements.push(`${faults.length}/${total}`);
else footerElements.push(`${faults.length}`); else footerElements.push(`${faults.length}`);
@@ -161,12 +165,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -217,13 +222,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/faults", ops); m.route.set("/faults", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/faults", ops); m.route.set("/faults", ops);
} }
@@ -232,16 +237,17 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let faults = store.fetch("faults", filter, { const faults = store.fetch("faults", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("faults", filter); const count = store.count("faults", filter);
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let f of faults.value) for (const f of faults.value)
if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]); if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]);
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter); const downloadUrl = getDownloadUrl(vnode.attrs.filter);
+37 -33
View File
@@ -37,7 +37,7 @@ const attributes = [
{ id: "metadata.version", label: "Version" } { id: "metadata.version", label: "Version" }
]; ];
let formData = { const formData = {
resource: "files", resource: "files",
attributes: attributes attributes: attributes
.slice(1) // remove _id from new object form .slice(1) // remove _id from new object form
@@ -55,21 +55,21 @@ const unpackSmartQuery = memoize(query => {
function putActionHandler(action, object) { function putActionHandler(action, object) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (action === "save") { if (action === "save") {
let file = object["file"] ? object["file"][0] : null; const file = object["file"] ? object["file"][0] : null;
delete object["file"]; delete object["file"];
if (!file) return reject(new Error("File not selected")); if (!file) return void reject(new Error("File not selected"));
let id = file.name; const id = file.name;
store store
.resourceExists("files", id) .resourceExists("files", id)
.then(exists => { .then(exists => {
if (exists) { if (exists) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return reject(new Error("File already exists")); return void reject(new Error("File already exists"));
} }
let headers = Object.assign( const headers = Object.assign(
{ {
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
Accept: "application/octet-stream" Accept: "application/octet-stream"
@@ -81,9 +81,7 @@ function putActionHandler(action, object) {
method: "PUT", method: "PUT",
headers: headers, headers: headers,
url: `/api/files/${encodeURIComponent(id)}`, url: `/api/files/${encodeURIComponent(id)}`,
serialize: body => { serialize: body => body, // Identity function to prevent JSON.parse on blob data
return body; // Identity function to prevent JSON.parse on blob data
},
data: file data: file
}) })
.then(() => { .then(() => {
@@ -104,8 +102,8 @@ function putActionHandler(action, object) {
} }
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize(filter => {
let cols = {}; const cols = {};
for (let attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `/api/files.csv?${m.buildQueryString({ return `/api/files.csv?${m.buildQueryString({
filter: filter, filter: filter,
columns: JSON.stringify(cols) columns: JSON.stringify(cols)
@@ -113,10 +111,11 @@ const getDownloadUrl = memoize(filter => {
}); });
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("files", 2)) if (!window.authorizer.hasAccess("files", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
const sort = args.sort; const sort = args.sort;
const filter = args.filter; const filter = args.filter;
@@ -137,16 +136,17 @@ function renderTable(
type: "checkbox", type: "checkbox",
checked: files.length && selected.size === files.length, checked: files.length && selected.size === files.length,
onchange: e => { onchange: e => {
for (let file of files) for (const file of files) {
if (e.target.checked) selected.add(file["_id"]); if (e.target.checked) selected.add(file["_id"]);
else selected.delete(file["_id"]); else selected.delete(file["_id"]);
}
}, },
disabled: !total disabled: !total
}); });
const labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let attr of attributes) { for (const attr of attributes) {
let label = attr.label; const label = attr.label;
let direction = 1; let direction = 1;
@@ -154,7 +154,7 @@ function renderTable(
if (sort[attr.id] > 0) symbol = "\u2bc6"; if (sort[attr.id] > 0) symbol = "\u2bc6";
else if (sort[attr.id] < 0) symbol = "\u2bc5"; else if (sort[attr.id] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -168,9 +168,9 @@ function renderTable(
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
} }
let rows = []; const rows = [];
for (let file of files) { for (const file of files) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(file["_id"]), checked: selected.has(file["_id"]),
onchange: e => { onchange: e => {
@@ -183,11 +183,12 @@ function renderTable(
} }
}); });
let tds = [m("td", checkbox)]; const tds = [m("td", checkbox)];
for (let attr of attributes) for (const attr of attributes) {
if (attr.id == "script") if (attr.id == "script")
tds.push(m("td", { title: file[attr.id] }, file[attr.id])); tds.push(m("td", { title: file[attr.id] }, file[attr.id]));
else tds.push(m("td", file[attr.id])); else tds.push(m("td", file[attr.id]));
}
rows.push( rows.push(
m( m(
@@ -210,7 +211,7 @@ function renderTable(
if (!rows.length) if (!rows.length)
rows.push(m("tr.empty", m("td", { colspan: labels.length }, "No files"))); rows.push(m("tr.empty", m("td", { colspan: labels.length }, "No files")));
let footerElements = []; const footerElements = [];
if (total != null) footerElements.push(`${files.length}/${total}`); if (total != null) footerElements.push(`${files.length}/${total}`);
else footerElements.push(`${files.length}`); else footerElements.push(`${files.length}`);
@@ -226,12 +227,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -261,14 +263,14 @@ function renderTable(
) )
]; ];
if (window.authorizer.hasAccess("files", 3)) if (window.authorizer.hasAccess("files", 3)) {
buttons.push( buttons.push(
m( m(
"button.primary", "button.primary",
{ {
title: "Create new file", title: "Create new file",
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -295,6 +297,7 @@ function renderTable(
"New" "New"
) )
); );
}
return [ return [
m( m(
@@ -317,13 +320,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/files", ops); m.route.set("/files", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/files", ops); m.route.set("/files", ops);
} }
@@ -332,17 +335,18 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let files = store.fetch("files", filter, { const files = store.fetch("files", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("files", filter); const count = store.count("files", filter);
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let file of files.value) for (const file of files.value)
if (vnode.state.selected.has(file["_id"])) selected.add(file["_id"]); if (vnode.state.selected.has(file["_id"])) selected.add(file["_id"]);
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter); const downloadUrl = getDownloadUrl(vnode.attrs.filter);
+4 -3
View File
@@ -17,8 +17,8 @@ const getAutocomplete = memoize(resource => {
function parseFilter(f) { function parseFilter(f) {
if (/^[\s0-9a-zA-Z]+:/.test(f)) { if (/^[\s0-9a-zA-Z]+:/.test(f)) {
let k = f.split(":", 1)[0]; const k = f.split(":", 1)[0];
let v = f.slice(k.length + 1).trim(); const v = f.slice(k.length + 1).trim();
return ["FUNC", "Q", k.trim(), v]; return ["FUNC", "Q", k.trim(), v];
} }
return expression.parse(f); return expression.parse(f);
@@ -35,7 +35,7 @@ const splitFilter = memoize(filter => {
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(stringifyFilter(ff)); for (const ff of f.slice(1)) list.push(stringifyFilter(ff));
else list.push(stringifyFilter(f)); else list.push(stringifyFilter(f));
list.push(""); list.push("");
@@ -59,6 +59,7 @@ const component = {
} catch (err) { } catch (err) {
vnode.state.filterInvalid |= 1 << idx; vnode.state.filterInvalid |= 1 << idx;
} }
return null;
}); });
vnode.state.filterList.push(""); vnode.state.filterList.push("");
+15 -8
View File
@@ -6,8 +6,8 @@ const menu = {
view: vnode => { view: vnode => {
const active = { [vnode.attrs.page]: "active" }; const active = { [vnode.attrs.page]: "active" };
let tabs = []; const tabs = [];
if (window.authorizer.hasAccess("devices", 1)) if (window.authorizer.hasAccess("devices", 1)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -15,8 +15,9 @@ const menu = {
m("a", { href: "#!/overview" }, "Overview") m("a", { href: "#!/overview" }, "Overview")
) )
); );
}
if (window.authorizer.hasAccess("devices", 2)) if (window.authorizer.hasAccess("devices", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -24,8 +25,9 @@ const menu = {
m("a", { href: "#!/devices" }, "Devices") m("a", { href: "#!/devices" }, "Devices")
) )
); );
}
if (window.authorizer.hasAccess("faults", 2)) if (window.authorizer.hasAccess("faults", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -33,8 +35,9 @@ const menu = {
m("a", { href: "#!/faults" }, "Faults") m("a", { href: "#!/faults" }, "Faults")
) )
); );
}
if (window.authorizer.hasAccess("presets", 2)) if (window.authorizer.hasAccess("presets", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -42,8 +45,9 @@ const menu = {
m("a", { href: "#!/presets" }, "Presets") m("a", { href: "#!/presets" }, "Presets")
) )
); );
}
if (window.authorizer.hasAccess("provisions", 2)) if (window.authorizer.hasAccess("provisions", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -51,8 +55,9 @@ const menu = {
m("a", { href: "#!/provisions" }, "Provisions") m("a", { href: "#!/provisions" }, "Provisions")
) )
); );
}
if (window.authorizer.hasAccess("virtualParameters", 2)) if (window.authorizer.hasAccess("virtualParameters", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -60,8 +65,9 @@ const menu = {
m("a", { href: "#!/virtualParameters" }, "Virtual Parameters") m("a", { href: "#!/virtualParameters" }, "Virtual Parameters")
) )
); );
}
if (window.authorizer.hasAccess("files", 2)) if (window.authorizer.hasAccess("files", 2)) {
tabs.push( tabs.push(
m( m(
"li", "li",
@@ -69,6 +75,7 @@ const menu = {
m("a", { href: "#!/files" }, "Files") m("a", { href: "#!/files" }, "Files")
) )
); );
}
return m("nav", m("ul", tabs)); return m("nav", m("ul", tabs));
} }
+2 -1
View File
@@ -18,7 +18,7 @@ function close(callback) {
} }
function render() { function render() {
if (overlayCallback) if (overlayCallback) {
return m( return m(
".overlay-wrapper", ".overlay-wrapper",
{ {
@@ -50,6 +50,7 @@ function render() {
overlayCallback() overlayCallback()
) )
); );
}
return null; return null;
} }
+10 -8
View File
@@ -11,9 +11,10 @@ const memoizedParse = memoize(expression.parse);
const GROUPS = config.ui.overview.groups; const GROUPS = config.ui.overview.groups;
const CHARTS = {}; const CHARTS = {};
for (let group of Object.values(GROUPS)) for (const group of Object.values(GROUPS)) {
for (let chartName of Object.values(group["charts"])) for (const chartName of Object.values(group["charts"]))
CHARTS[chartName] = config.ui.overview.charts[chartName]; CHARTS[chartName] = config.ui.overview.charts[chartName];
}
function queryCharts(charts) { function queryCharts(charts) {
charts = Object.assign({}, charts); charts = Object.assign({}, charts);
@@ -30,10 +31,11 @@ function queryCharts(charts) {
} }
const init = function() { const init = function() {
if (!window.authorizer.hasAccess("devices", 1)) if (!window.authorizer.hasAccess("devices", 1)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
return Promise.resolve({ charts: queryCharts(CHARTS) }); return Promise.resolve({ charts: queryCharts(CHARTS) });
}; };
@@ -41,14 +43,14 @@ const init = function() {
const component = { const component = {
view: vnode => { view: vnode => {
document.title = "Overview - GenieACS"; document.title = "Overview - GenieACS";
let children = []; const children = [];
for (let group of Object.values(GROUPS)) { for (const group of Object.values(GROUPS)) {
if (group.label) children.push(m("h1", group.label)); if (group.label) children.push(m("h1", group.label));
let groupChildren = []; const groupChildren = [];
for (let chartName of Object.values(group.charts)) { for (const chartName of Object.values(group.charts)) {
const chart = vnode.attrs.charts[chartName]; const chart = vnode.attrs.charts[chartName];
let chartChildren = []; const chartChildren = [];
if (chart.label) chartChildren.push(m("h2", chart.label)); if (chart.label) chartChildren.push(m("h2", chart.label));
chartChildren.push(m(pieChartComponent, { chart: chart })); chartChildren.push(m(pieChartComponent, { chart: chart }));
+11 -11
View File
@@ -3,21 +3,21 @@
import m from "mithril"; import m from "mithril";
const drawChart = function(chartData) { const drawChart = function(chartData) {
let slices = chartData.slices; const slices = chartData.slices;
let total = Array.from(Object.values(chartData.slices)).reduce( const total = Array.from(Object.values(chartData.slices)).reduce(
(a, s) => a + (s.count.value || 0), (a, s) => a + (s.count.value || 0),
0 0
); );
let legend = []; const legend = [];
let paths = []; const paths = [];
let links = []; const links = [];
let currentProgressPercentage = 0; let currentProgressPercentage = 0;
let startX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100; let startX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100;
let startY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100; let startY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100;
let endX, endY; let endX, endY;
for (let slice of Object.values(slices)) { for (const slice of Object.values(slices)) {
let percent = total > 0 ? (slice.count.value || 0) / total : 0; const percent = total > 0 ? (slice.count.value || 0) / total : 0;
legend.push( legend.push(
m(".legend-line", [ m(".legend-line", [
m("span.color", { m("span.color", {
@@ -41,9 +41,9 @@ const drawChart = function(chartData) {
currentProgressPercentage += percent; currentProgressPercentage += percent;
endX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100; endX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100;
endY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100; endY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100;
let isBigArc = percent > 0.5 ? 1 : 0; const isBigArc = percent > 0.5 ? 1 : 0;
let sketch = const sketch =
`M ${startX} ${startY} ` + // Move to the starting point `M ${startX} ${startY} ` + // Move to the starting point
`A 100 100 0 ${isBigArc} 1 ${endX} ${endY} ` + // Draw an Arc from starting point to ending point `A 100 100 0 ${isBigArc} 1 ${endX} ${endY} ` + // Draw an Arc from starting point to ending point
`L 0 0 z`; // complete the shape by drawing a line to the center of circle `L 0 0 z`; // complete the shape by drawing a line to the center of circle
@@ -58,9 +58,9 @@ const drawChart = function(chartData) {
}) })
); );
let percentageX = const percentageX =
Math.cos(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50; Math.cos(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50;
let percentageY = const percentageY =
Math.sin(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50; Math.sin(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50;
links.push( links.push(
+42 -32
View File
@@ -39,10 +39,10 @@ const unpackSmartQuery = memoize(query => {
function putActionHandler(action, object, isNew) { function putActionHandler(action, object, isNew) {
if (action === "save") { if (action === "save") {
let id = object["_id"]; const id = object["_id"];
delete object["_id"]; delete object["_id"];
if (!id) return notifications.push("error", "ID can not be empty"); if (!id) return void notifications.push("error", "ID can not be empty");
store store
.resourceExists("presets", id) .resourceExists("presets", id)
@@ -90,14 +90,14 @@ function putActionHandler(action, object, isNew) {
} }
} }
let formData = { const formData = {
resource: "presets", resource: "presets",
attributes: attributes attributes: attributes
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize(filter => {
let cols = {}; const cols = {};
for (let attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `/api/presets.csv?${m.buildQueryString({ return `/api/presets.csv?${m.buildQueryString({
filter: filter, filter: filter,
columns: JSON.stringify(cols) columns: JSON.stringify(cols)
@@ -105,10 +105,11 @@ const getDownloadUrl = memoize(filter => {
}); });
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("presets", 2)) if (!window.authorizer.hasAccess("presets", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
const sort = args.sort; const sort = args.sort;
const filter = args.filter; const filter = args.filter;
@@ -129,17 +130,18 @@ function renderTable(
type: "checkbox", type: "checkbox",
checked: presets.length && selected.size === presets.length, checked: presets.length && selected.size === presets.length,
onchange: e => { onchange: e => {
for (let preset of presets) for (const preset of presets) {
if (e.target.checked) selected.add(preset["_id"]); if (e.target.checked) selected.add(preset["_id"]);
else selected.delete(preset["_id"]); else selected.delete(preset["_id"]);
}
}, },
disabled: !total disabled: !total
}); });
const labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let attr of attributes) { for (const attr of attributes) {
let label = attr.label; const label = attr.label;
if (attr.unsortable) { if (attr.unsortable) {
labels.push(m("th", label)); labels.push(m("th", label));
@@ -152,7 +154,7 @@ function renderTable(
if (sort[attr.id] > 0) symbol = "\u2bc6"; if (sort[attr.id] > 0) symbol = "\u2bc6";
else if (sort[attr.id] < 0) symbol = "\u2bc5"; else if (sort[attr.id] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -166,9 +168,9 @@ function renderTable(
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
} }
let rows = []; const rows = [];
for (let preset of presets) { for (const preset of presets) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(preset["_id"]), checked: selected.has(preset["_id"]),
onchange: e => { onchange: e => {
@@ -182,14 +184,15 @@ function renderTable(
}); });
let devicesUrl = "/#!/devices"; let devicesUrl = "/#!/devices";
if (preset["precondition"].length) if (preset["precondition"].length) {
devicesUrl += `?${m.buildQueryString({ devicesUrl += `?${m.buildQueryString({
filter: preset["precondition"] filter: preset["precondition"]
})}`; })}`;
}
let tds = [m("td", checkbox)]; const tds = [m("td", checkbox)];
for (let attr of attributes) for (const attr of attributes) {
if (attr.id === "precondition") if (attr.id === "precondition") {
tds.push( tds.push(
m( m(
"td", "td",
@@ -197,7 +200,10 @@ function renderTable(
m("a", { href: devicesUrl }, preset[attr.id]) m("a", { href: devicesUrl }, preset[attr.id])
) )
); );
else tds.push(m("td", preset[attr.id])); } else {
tds.push(m("td", preset[attr.id]));
}
}
tds.push( tds.push(
m( m(
@@ -206,7 +212,7 @@ function renderTable(
"a", "a",
{ {
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -250,7 +256,7 @@ function renderTable(
if (!rows.length) if (!rows.length)
rows.push(m("tr.empty", m("td", { colspan: labels.length }, "No presets"))); rows.push(m("tr.empty", m("td", { colspan: labels.length }, "No presets")));
let footerElements = []; const footerElements = [];
if (total != null) footerElements.push(`${presets.length}/${total}`); if (total != null) footerElements.push(`${presets.length}/${total}`);
else footerElements.push(`${presets.length}`); else footerElements.push(`${presets.length}`);
@@ -266,12 +272,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -301,14 +308,14 @@ function renderTable(
) )
]; ];
if (window.authorizer.hasAccess("presets", 3)) if (window.authorizer.hasAccess("presets", 3)) {
buttons.push( buttons.push(
m( m(
"button.primary", "button.primary",
{ {
title: "Create new preset", title: "Create new preset",
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -328,6 +335,7 @@ function renderTable(
"New" "New"
) )
); );
}
return [ return [
m( m(
@@ -350,13 +358,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/presets", ops); m.route.set("/presets", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/presets", ops); m.route.set("/presets", ops);
} }
@@ -365,15 +373,15 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let presets = store.fetch("presets", filter, { const presets = store.fetch("presets", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("presets", filter); const count = store.count("presets", filter);
const provisions = store.fetch("provisions", true); const provisions = store.fetch("provisions", true);
if (provisions.fulfilled) { if (provisions.fulfilled) {
let provisionAttr = attributes.find(attr => { const provisionAttr = attributes.find(attr => {
return attr.id === "provision"; return attr.id === "provision";
}); });
provisionAttr.options = provisions.value.map(v => { provisionAttr.options = provisions.value.map(v => {
@@ -381,11 +389,13 @@ const component = {
}); });
} }
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let preset of presets.value) for (const preset of presets.value) {
if (vnode.state.selected.has(preset["_id"])) if (vnode.state.selected.has(preset["_id"]))
selected.add(preset["_id"]); selected.add(preset["_id"]);
}
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter); const downloadUrl = getDownloadUrl(vnode.attrs.filter);
+37 -29
View File
@@ -34,10 +34,10 @@ const unpackSmartQuery = memoize(query => {
function putActionHandler(action, object, isNew) { function putActionHandler(action, object, isNew) {
if (action === "save") { if (action === "save") {
let id = object["_id"]; const id = object["_id"];
delete object["_id"]; delete object["_id"];
if (!id) return notifications.push("error", "ID can not be empty"); if (!id) return void notifications.push("error", "ID can not be empty");
store store
.resourceExists("provisions", id) .resourceExists("provisions", id)
@@ -85,14 +85,14 @@ function putActionHandler(action, object, isNew) {
} }
} }
let formData = { const formData = {
resource: "provisions", resource: "provisions",
attributes: attributes attributes: attributes
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize(filter => {
let cols = {}; const cols = {};
for (let attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `/api/provisions.csv?${m.buildQueryString({ return `/api/provisions.csv?${m.buildQueryString({
filter: filter, filter: filter,
columns: JSON.stringify(cols) columns: JSON.stringify(cols)
@@ -100,10 +100,11 @@ const getDownloadUrl = memoize(filter => {
}); });
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("provisions", 2)) if (!window.authorizer.hasAccess("provisions", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
const sort = args.sort; const sort = args.sort;
const filter = args.filter; const filter = args.filter;
@@ -131,16 +132,17 @@ function renderTable(
type: "checkbox", type: "checkbox",
checked: provisions.length && selected.size === provisions.length, checked: provisions.length && selected.size === provisions.length,
onchange: e => { onchange: e => {
for (let provision of provisions) for (const provision of provisions) {
if (e.target.checked) selected.add(provision["_id"]); if (e.target.checked) selected.add(provision["_id"]);
else selected.delete(provision["_id"]); else selected.delete(provision["_id"]);
}
}, },
disabled: !total disabled: !total
}); });
const labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let attr of attributes) { for (const attr of attributes) {
let label = attr.label; const label = attr.label;
let direction = 1; let direction = 1;
@@ -148,7 +150,7 @@ function renderTable(
if (sort[attr.id] > 0) symbol = "\u2bc6"; if (sort[attr.id] > 0) symbol = "\u2bc6";
else if (sort[attr.id] < 0) symbol = "\u2bc5"; else if (sort[attr.id] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -162,9 +164,9 @@ function renderTable(
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
} }
let rows = []; const rows = [];
for (let provision of provisions) { for (const provision of provisions) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(provision["_id"]), checked: selected.has(provision["_id"]),
onchange: e => { onchange: e => {
@@ -177,11 +179,12 @@ function renderTable(
} }
}); });
let tds = [m("td", checkbox)]; const tds = [m("td", checkbox)];
for (let attr of attributes) for (const attr of attributes) {
if (attr.id == "script") if (attr.id == "script")
tds.push(m("td", { title: provision[attr.id] }, provision[attr.id])); tds.push(m("td", { title: provision[attr.id] }, provision[attr.id]));
else tds.push(m("td", provision[attr.id])); else tds.push(m("td", provision[attr.id]));
}
tds.push( tds.push(
m( m(
@@ -190,7 +193,7 @@ function renderTable(
"a", "a",
{ {
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -232,12 +235,13 @@ function renderTable(
); );
} }
if (!rows.length) if (!rows.length) {
rows.push( rows.push(
m("tr.empty", m("td", { colspan: labels.length }, "No provisions")) m("tr.empty", m("td", { colspan: labels.length }, "No provisions"))
); );
}
let footerElements = []; const footerElements = [];
if (total != null) footerElements.push(`${provisions.length}/${total}`); if (total != null) footerElements.push(`${provisions.length}/${total}`);
else footerElements.push(`${provisions.length}`); else footerElements.push(`${provisions.length}`);
@@ -253,12 +257,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -290,14 +295,14 @@ function renderTable(
) )
]; ];
if (window.authorizer.hasAccess("provisions", 3)) if (window.authorizer.hasAccess("provisions", 3)) {
buttons.push( buttons.push(
m( m(
"button.primary", "button.primary",
{ {
title: "Create new provision", title: "Create new provision",
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -317,6 +322,7 @@ function renderTable(
"New" "New"
) )
); );
}
return [ return [
m( m(
@@ -339,13 +345,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/provisions", ops); m.route.set("/provisions", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/provisions", ops); m.route.set("/provisions", ops);
} }
@@ -354,18 +360,20 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let provisions = store.fetch("provisions", filter, { const provisions = store.fetch("provisions", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("provisions", filter); const count = store.count("provisions", filter);
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let provision of provisions.value) for (const provision of provisions.value) {
if (vnode.state.selected.has(provision["_id"])) if (vnode.state.selected.has(provision["_id"]))
selected.add(provision["_id"]); selected.add(provision["_id"]);
}
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter); const downloadUrl = getDownloadUrl(vnode.attrs.filter);
+12 -9
View File
@@ -3,7 +3,7 @@
import config from "./config"; import config from "./config";
import * as expression from "../common/expression"; import * as expression from "../common/expression";
let resources = { const resources = {
devices: {}, devices: {},
faults: { faults: {
Device: { parameter: ["PARAM", "device"], type: "string" }, Device: { parameter: ["PARAM", "device"], type: "string" },
@@ -35,11 +35,12 @@ let resources = {
} }
}; };
for (let v of Object.values(config.ui.filters)) for (const v of Object.values(config.ui.filters)) {
resources.devices[v.label] = { resources.devices[v.label] = {
parameter: expression.parse(v.parameter), parameter: expression.parse(v.parameter),
type: (v.type || "").split(",").map(s => s.trim()) type: (v.type || "").split(",").map(s => s.trim())
}; };
}
function getLabels(resource) { function getLabels(resource) {
if (!resources[resource]) return []; if (!resources[resource]) return [];
@@ -48,14 +49,15 @@ function getLabels(resource) {
function queryNumber(param, value) { function queryNumber(param, value) {
let op = "="; let op = "=";
for (let o of ["<>", "=", "<=", "<", ">=", ">"]) for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
if (value.startsWith(o)) { if (value.startsWith(o)) {
op = o; op = o;
value = value.slice(o.length).trim(); value = value.slice(o.length).trim();
break; break;
} }
}
let v = parseInt(value); const v = parseInt(value);
if (v !== +value) return null; if (v !== +value) return null;
return [op, param, v]; return [op, param, v];
@@ -68,12 +70,13 @@ function queryString(param, value) {
function queryMac(param, value) { function queryMac(param, value) {
value = value.replace(/[^a-f0-9]/gi, "").toLowerCase(); value = value.replace(/[^a-f0-9]/gi, "").toLowerCase();
if (!value) return null; if (!value) return null;
if (value.length === 12) if (value.length === 12) {
return [ return [
"LIKE", "LIKE",
["FUNC", "LOWER", param], ["FUNC", "LOWER", param],
value.replace(/(..)(?!$)/g, "$1:") value.replace(/(..)(?!$)/g, "$1:")
]; ];
}
return [ return [
"OR", "OR",
@@ -90,20 +93,20 @@ function unpack(resource, label, value) {
if (!resources[resource]) return null; if (!resources[resource]) return null;
const type = resources[resource][label].type; const type = resources[resource][label].type;
value = value.trim(); value = value.trim();
let res = ["OR"]; const res = ["OR"];
if (type.length === 0 || type.includes("number")) { if (type.length === 0 || type.includes("number")) {
let q = queryNumber(resources[resource][label].parameter, value); const q = queryNumber(resources[resource][label].parameter, value);
if (q) res.push(q); if (q) res.push(q);
} }
if (type.length === 0 || type.includes("string")) { if (type.length === 0 || type.includes("string")) {
let q = queryString(resources[resource][label].parameter, value); const q = queryString(resources[resource][label].parameter, value);
if (q) res.push(q); if (q) res.push(q);
} }
if (type.includes("mac")) { if (type.includes("mac")) {
let q = queryMac(resources[resource][label].parameter, value); const q = queryMac(resources[resource][label].parameter, value);
if (q) res.push(q); if (q) res.push(q);
} }
+43 -32
View File
@@ -1,7 +1,6 @@
"use strict"; "use strict";
import m from "mithril"; import m from "mithril";
import config from "./config";
import * as expressionParser from "../common/expression-parser"; import * as expressionParser from "../common/expression-parser";
import * as expression from "../common/expression"; import * as expression from "../common/expression";
import memoize from "../common/memoize"; import memoize from "../common/memoize";
@@ -23,7 +22,7 @@ const queries = {
}; };
const resources = {}; const resources = {};
for (let r of [ for (const r of [
"devices", "devices",
"faults", "faults",
"files", "files",
@@ -31,13 +30,14 @@ for (let r of [
"provisions", "provisions",
"virtualParameters", "virtualParameters",
"files" "files"
]) ]) {
resources[r] = { resources[r] = {
objects: new Map(), objects: new Map(),
count: new Map(), count: new Map(),
fetch: new Map(), fetch: new Map(),
combinedFilter: null combinedFilter: null
}; };
}
class QueryResponse { class QueryResponse {
get fulfilled() { get fulfilled() {
@@ -79,11 +79,12 @@ function limitFilter(filter, sort, bookmark) {
arr.reduce((cur, kv) => { arr.reduce((cur, kv) => {
const [param, asc] = kv; const [param, asc] = kv;
if (asc <= 0) { if (asc <= 0) {
if (bookmark[param] == null) if (bookmark[param] == null) {
return expression.or( return expression.or(
["IS NOT NULL", ["PARAM", param]], ["IS NOT NULL", ["PARAM", param]],
expression.and(["IS NULL", ["PARAM", param]], cur) expression.and(["IS NULL", ["PARAM", param]], cur)
); );
}
let f = null; let f = null;
@@ -124,13 +125,15 @@ function compareFunction(sort) {
for (const [param, asc] of sortEntries) { for (const [param, asc] of sortEntries) {
let v1 = a[param]; let v1 = a[param];
let v2 = b[param]; let v2 = b[param];
if (v1 != null && typeof v1 === "object") if (v1 != null && typeof v1 === "object") {
if (v1.value) v1 = v1.value[0]; if (v1.value) v1 = v1.value[0];
else v1 = null; else v1 = null;
}
if (v2 != null && typeof v2 === "object") if (v2 != null && typeof v2 === "object") {
if (v2.value) v2 = v2.value[0]; if (v2.value) v2 = v2.value[0];
else v2 = null; else v2 = null;
}
if (v1 > v2) { if (v1 > v2) {
return asc; return asc;
@@ -153,23 +156,24 @@ function compareFunction(sort) {
function findMatches(resourceType, filter, sort, limit) { function findMatches(resourceType, filter, sort, limit) {
// Handle "tag =" and "tag <>" special cases // Handle "tag =" and "tag <>" special cases
if (resourceType === "devices") if (resourceType === "devices") {
filter = expressionParser.map(filter, e => { filter = expressionParser.map(filter, e => {
if ( if (
Array.isArray(e) && Array.isArray(e) &&
Array.isArray(e[1]) && Array.isArray(e[1]) &&
e[1][0] === "PARAM" && e[1][0] === "PARAM" &&
e[1][1] === "tag" e[1][1] === "tag"
) ) {
if (e[0] === "=") return ["IS NOT NULL", ["PARAM", `Tags.${e[2]}`]]; if (e[0] === "=") return ["IS NOT NULL", ["PARAM", `Tags.${e[2]}`]];
else if (e[0] === "<>") return ["IS NULL", ["PARAM", `Tags.${e[2]}`]]; else if (e[0] === "<>") return ["IS NULL", ["PARAM", `Tags.${e[2]}`]];
}
return e; return e;
}); });
}
let value = []; let value = [];
for (let obj of resources[resourceType].objects.values()) for (const obj of resources[resourceType].objects.values())
if (expression.evaluate(filter, obj, fulfillTimestamp)) if (expression.evaluate(filter, obj, fulfillTimestamp)) value.push(obj);
value.push(obj);
value = value.sort(compareFunction(sort)); value = value.sort(compareFunction(sort));
if (limit) value = value.slice(0, limit); if (limit) value = value.slice(0, limit);
@@ -181,7 +185,7 @@ function inferQuery(resourceType, queryResponse) {
const limit = queries.limit.get(queryResponse); const limit = queries.limit.get(queryResponse);
let filter = queries.filter.get(queryResponse); let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter); filter = unpackExpression(filter);
let bookmark = queries.bookmark.get(queryResponse); const bookmark = queries.bookmark.get(queryResponse);
const sort = queries.sort.get(queryResponse); const sort = queries.sort.get(queryResponse);
if (bookmark || !limit) { if (bookmark || !limit) {
if (bookmark) filter = limitFilter(filter, sort, bookmark); if (bookmark) filter = limitFilter(filter, sort, bookmark);
@@ -208,7 +212,7 @@ function fetch(resourceType, filter, options = {}) {
sort["DeviceID.ID"] = sort["DeviceID.ID"] || 1; sort["DeviceID.ID"] = sort["DeviceID.ID"] || 1;
else sort["_id"] = sort["_id"] || 1; else sort["_id"] = sort["_id"] || 1;
let key = `${filterStr}:${limit}:${JSON.stringify(sort)}`; const key = `${filterStr}:${limit}:${JSON.stringify(sort)}`;
let queryResponse = resources[resourceType].fetch.get(key); let queryResponse = resources[resourceType].fetch.get(key);
if (queryResponse) return queryResponse; if (queryResponse) return queryResponse;
@@ -225,15 +229,15 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
let updated = false; let updated = false;
if (_fulfillTimestamp > fulfillTimestamp) { if (_fulfillTimestamp > fulfillTimestamp) {
for (let resource of Object.values(resources)) for (const resource of Object.values(resources))
resource.combinedFilter = null; resource.combinedFilter = null;
fulfillTimestamp = _fulfillTimestamp; fulfillTimestamp = _fulfillTimestamp;
} }
const allPromises = []; const allPromises = [];
for (let [resourceType, resource] of Object.entries(resources)) for (const [resourceType, resource] of Object.entries(resources)) {
for (let [queryResponseKey, queryResponse] of resource.count) { for (const [queryResponseKey, queryResponse] of resource.count) {
if (!(queries.accessed.get(queryResponse) >= accessTimestamp)) { if (!(queries.accessed.get(queryResponse) >= accessTimestamp)) {
resource.count.delete(queryResponseKey); resource.count.delete(queryResponseKey);
continue; continue;
@@ -269,11 +273,12 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
); );
} }
} }
}
let toFetchAll = {}; const toFetchAll = {};
for (let [resourceType, resource] of Object.entries(resources)) for (const [resourceType, resource] of Object.entries(resources)) {
for (let [queryResponseKey, queryResponse] of resource.fetch) { for (const [queryResponseKey, queryResponse] of resource.fetch) {
if (!(queries.accessed.get(queryResponse) >= accessTimestamp)) { if (!(queries.accessed.get(queryResponse) >= accessTimestamp)) {
resource.fetch.delete(queryResponseKey); resource.fetch.delete(queryResponseKey);
continue; continue;
@@ -287,7 +292,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
toFetchAll[resourceType].push(queryResponse); toFetchAll[resourceType].push(queryResponse);
const limit = queries.limit.get(queryResponse); const limit = queries.limit.get(queryResponse);
const sort = queries.sort.get(queryResponse); const sort = queries.sort.get(queryResponse);
if (limit) if (limit) {
allPromises.push( allPromises.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
updated = true; updated = true;
@@ -308,12 +313,15 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
.then(res => { .then(res => {
if (res.length) { if (res.length) {
// Generate bookmark object // Generate bookmark object
let bm = Object.keys(sort).reduce((b, k) => { const bm = Object.keys(sort).reduce((b, k) => {
if (res[0][k] != null) if (res[0][k] != null) {
if (typeof res[0][k] === "object") { if (typeof res[0][k] === "object") {
if (res[0][k].value != null) if (res[0][k].value != null)
b[k] = res[0][k].value[0]; b[k] = res[0][k].value[0];
} else b[k] = res[0][k]; } else {
b[k] = res[0][k];
}
}
return b; return b;
}, {}); }, {});
@@ -326,8 +334,10 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
.catch(reject); .catch(reject);
}) })
); );
}
} }
} }
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Promise.all(allPromises) Promise.all(allPromises)
@@ -368,11 +378,12 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
if (!resources[resourceType].combinedFilter) if (!resources[resourceType].combinedFilter)
deleted = new Set(resources[resourceType].objects.keys()); deleted = new Set(resources[resourceType].objects.keys());
let combinedFilterDiff = combinedFilter; let combinedFilterDiff = combinedFilter;
if (resources[resourceType].combinedFilter) if (resources[resourceType].combinedFilter) {
combinedFilterDiff = expression.and( combinedFilterDiff = expression.and(
combinedFilterDiff, combinedFilterDiff,
expression.not(resources[resourceType].combinedFilter) expression.not(resources[resourceType].combinedFilter)
); );
}
resources[resourceType].combinedFilter = expression.or( resources[resourceType].combinedFilter = expression.or(
combinedFilter, combinedFilter,
resources[resourceType].combinedFilter resources[resourceType].combinedFilter
@@ -389,7 +400,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
}) })
}) })
.then(res => { .then(res => {
for (let r of res) { for (const r of res) {
const id = const id =
resourceType === "devices" resourceType === "devices"
? r["DeviceID.ID"].value[0] ? r["DeviceID.ID"].value[0]
@@ -398,7 +409,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
deleted.delete(id); deleted.delete(id);
} }
for (let d of deleted) { for (const d of deleted) {
const obj = resources[resourceType].objects.get(d); const obj = resources[resourceType].objects.get(d);
if ( if (
expression.evaluate( expression.evaluate(
@@ -410,7 +421,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
resources[resourceType].objects.delete(d); resources[resourceType].objects.delete(d);
} }
for (let queryResponse of toFetch) { for (const queryResponse of toFetch) {
let filter = queries.filter.get(queryResponse); let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter); filter = unpackExpression(filter);
const limit = queries.limit.get(queryResponse); const limit = queries.limit.get(queryResponse);
@@ -444,7 +455,7 @@ function getTimestamp() {
} }
function postTasks(deviceId, tasks) { function postTasks(deviceId, tasks) {
for (let t of tasks) { for (const t of tasks) {
t.status = "pending"; t.status = "pending";
t.device = deviceId; t.device = deviceId;
} }
@@ -459,8 +470,8 @@ function postTasks(deviceId, tasks) {
const connectionRequestStatus = xhr.getResponseHeader( const connectionRequestStatus = xhr.getResponseHeader(
"Connection-Request" "Connection-Request"
); );
let st = JSON.parse(xhr.response); const st = JSON.parse(xhr.response);
for (let [i, t] of st.entries()) { for (const [i, t] of st.entries()) {
tasks[i]._id = t._id; tasks[i]._id = t._id;
tasks[i].status = t.status; tasks[i].status = t.status;
tasks[i].fault = t.fault; tasks[i].fault = t.fault;
@@ -487,7 +498,7 @@ function deleteResource(resourceType, id) {
} }
function putResource(resourceType, id, object) { function putResource(resourceType, id, object) {
for (let k in object) if (object[k] === undefined) object[k] = null; for (const k in object) if (object[k] === undefined) object[k] = null;
return m.request({ return m.request({
method: "PUT", method: "PUT",
@@ -498,7 +509,7 @@ function putResource(resourceType, id, object) {
function resourceExists(resource, id) { function resourceExists(resource, id) {
const param = resource === "devices" ? "DeviceID.ID" : "_id"; const param = resource === "devices" ? "DeviceID.ID" : "_id";
let filter = ["=", ["PARAM", param], id]; const filter = ["=", ["PARAM", param], id];
return m.request({ return m.request({
method: "HEAD", method: "HEAD",
url: url:
+6 -5
View File
@@ -41,8 +41,8 @@ function stageDownload(task) {
} }
function commit(tasks, callback) { function commit(tasks, callback) {
let devices = {}; const devices = {};
for (let t of tasks) { for (const t of tasks) {
devices[t.device] = devices[t.device] || []; devices[t.device] = devices[t.device] || [];
devices[t.device].push(t); devices[t.device].push(t);
queueTask(t); queueTask(t);
@@ -50,19 +50,20 @@ function commit(tasks, callback) {
return new Promise(resolve => { return new Promise(resolve => {
let counter = 1; let counter = 1;
for (let [deviceId, tasks2] of Object.entries(devices)) { for (const [deviceId, tasks2] of Object.entries(devices)) {
++counter; ++counter;
store store
.postTasks(deviceId, tasks) .postTasks(deviceId, tasks)
.then(connectionRequestStatus => { .then(connectionRequestStatus => {
for (let t of tasks2) for (const t of tasks2) {
if (t.status === "pending") t.status = "stale"; if (t.status === "pending") t.status = "stale";
else if (t.status === "done") queue.delete(t); else if (t.status === "done") queue.delete(t);
}
callback(deviceId, null, connectionRequestStatus, tasks2); callback(deviceId, null, connectionRequestStatus, tasks2);
if (--counter === 0) resolve(); if (--counter === 0) resolve();
}) })
.catch(err => { .catch(err => {
for (let t of tasks2) t.status = "stale"; for (const t of tasks2) t.status = "stale";
callback(deviceId, err, null, tasks2); callback(deviceId, err, null, tasks2);
if (--counter === 0) resolve(); if (--counter === 0) resolve();
}); });
+2 -1
View File
@@ -13,7 +13,7 @@ function timeAgo(dtime) {
let res = ""; let res = "";
let level = 2; let level = 2;
for (const [u, t] of Object.entries(UNITS)) for (const [u, t] of Object.entries(UNITS)) {
if (dtime >= t) { if (dtime >= t) {
let n; let n;
if (level > 1) { if (level > 1) {
@@ -26,6 +26,7 @@ function timeAgo(dtime) {
else res += `${n} ${u} `; else res += `${n} ${u} `;
if (!--level) break; if (!--level) break;
} }
}
return res + "ago"; return res + "ago";
} }
+3 -2
View File
@@ -7,7 +7,7 @@ import * as notifications from "./notifications";
const userMenu = { const userMenu = {
view: () => { view: () => {
if (window.username) if (window.username) {
return m( return m(
"div.user-menu", "div.user-menu",
window.username, window.username,
@@ -32,7 +32,7 @@ const userMenu = {
"Log out" "Log out"
) )
); );
else } else {
return m( return m(
"div.user-menu", "div.user-menu",
m( m(
@@ -43,6 +43,7 @@ const userMenu = {
"Log in" "Log in"
) )
); );
}
} }
}; };
+41 -31
View File
@@ -34,10 +34,10 @@ const unpackSmartQuery = memoize(query => {
function putActionHandler(action, object, isNew) { function putActionHandler(action, object, isNew) {
if (action === "save") { if (action === "save") {
let id = object["_id"]; const id = object["_id"];
delete object["_id"]; delete object["_id"];
if (!id) return notifications.push("error", "ID can not be empty"); if (!id) return void notifications.push("error", "ID can not be empty");
store store
.resourceExists("virtualParameters", id) .resourceExists("virtualParameters", id)
@@ -85,14 +85,14 @@ function putActionHandler(action, object, isNew) {
} }
} }
let formData = { const formData = {
resource: "virtualParameters", resource: "virtualParameters",
attributes: attributes attributes: attributes
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize(filter => {
let cols = {}; const cols = {};
for (let attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `/api/virtualParameters.csv?${m.buildQueryString({ return `/api/virtualParameters.csv?${m.buildQueryString({
filter: filter, filter: filter,
columns: JSON.stringify(cols) columns: JSON.stringify(cols)
@@ -100,10 +100,11 @@ const getDownloadUrl = memoize(filter => {
}); });
function init(args) { function init(args) {
if (!window.authorizer.hasAccess("virtualParameters", 2)) if (!window.authorizer.hasAccess("virtualParameters", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
); );
}
const sort = args.sort; const sort = args.sort;
const filter = args.filter; const filter = args.filter;
@@ -132,16 +133,17 @@ function renderTable(
checked: checked:
virtualParameters.length && selected.size === virtualParameters.length, virtualParameters.length && selected.size === virtualParameters.length,
onchange: e => { onchange: e => {
for (let virtualParameter of virtualParameters) for (const virtualParameter of virtualParameters) {
if (e.target.checked) selected.add(virtualParameter["_id"]); if (e.target.checked) selected.add(virtualParameter["_id"]);
else selected.delete(virtualParameter["_id"]); else selected.delete(virtualParameter["_id"]);
}
}, },
disabled: !total disabled: !total
}); });
const labels = [m("th", selectAll)]; const labels = [m("th", selectAll)];
for (let attr of attributes) { for (const attr of attributes) {
let label = attr.label; const label = attr.label;
let direction = 1; let direction = 1;
@@ -149,7 +151,7 @@ function renderTable(
if (sort[attr.id] > 0) symbol = "\u2bc6"; if (sort[attr.id] > 0) symbol = "\u2bc6";
else if (sort[attr.id] < 0) symbol = "\u2bc5"; else if (sort[attr.id] < 0) symbol = "\u2bc5";
let sortable = m( const sortable = m(
"button", "button",
{ {
onclick: () => { onclick: () => {
@@ -163,9 +165,9 @@ function renderTable(
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
} }
let rows = []; const rows = [];
for (let virtualParameter of virtualParameters) { for (const virtualParameter of virtualParameters) {
let checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(virtualParameter["_id"]), checked: selected.has(virtualParameter["_id"]),
onchange: e => { onchange: e => {
@@ -178,9 +180,9 @@ function renderTable(
} }
}); });
let tds = [m("td", checkbox)]; const tds = [m("td", checkbox)];
for (let attr of attributes) for (const attr of attributes) {
if (attr.id == "script") if (attr.id == "script") {
tds.push( tds.push(
m( m(
"td", "td",
@@ -188,7 +190,10 @@ function renderTable(
virtualParameter[attr.id] virtualParameter[attr.id]
) )
); );
else tds.push(m("td", virtualParameter[attr.id])); } else {
tds.push(m("td", virtualParameter[attr.id]));
}
}
tds.push( tds.push(
m( m(
@@ -197,7 +202,7 @@ function renderTable(
"a", "a",
{ {
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -239,15 +244,16 @@ function renderTable(
); );
} }
if (!rows.length) if (!rows.length) {
rows.push( rows.push(
m( m(
"tr.empty", "tr.empty",
m("td", { colspan: labels.length }, "No virtual parameters") m("td", { colspan: labels.length }, "No virtual parameters")
) )
); );
}
let footerElements = []; const footerElements = [];
if (total != null) if (total != null)
footerElements.push(`${virtualParameters.length}/${total}`); footerElements.push(`${virtualParameters.length}/${total}`);
else footerElements.push(`${virtualParameters.length}`); else footerElements.push(`${virtualParameters.length}`);
@@ -266,12 +272,13 @@ function renderTable(
) )
); );
if (downloadUrl) if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download")
); );
}
let tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements))
); );
@@ -306,14 +313,14 @@ function renderTable(
) )
]; ];
if (window.authorizer.hasAccess("virtualParameters", 3)) if (window.authorizer.hasAccess("virtualParameters", 3)) {
buttons.push( buttons.push(
m( m(
"button.primary", "button.primary",
{ {
title: "Create new virtual parameter", title: "Create new virtual parameter",
onclick: () => { onclick: () => {
let cb = () => { const cb = () => {
return m( return m(
putForm, putForm,
Object.assign( Object.assign(
@@ -333,6 +340,7 @@ function renderTable(
"New" "New"
) )
); );
}
return [ return [
m( m(
@@ -355,13 +363,13 @@ const component = {
} }
function onFilterChanged(filter) { function onFilterChanged(filter) {
let ops = { filter }; const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/virtualParameters", ops); m.route.set("/virtualParameters", ops);
} }
function onSortChange(sort) { function onSortChange(sort) {
let ops = { sort }; const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/virtualParameters", ops); m.route.set("/virtualParameters", ops);
} }
@@ -370,18 +378,20 @@ const component = {
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true; let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter); filter = unpackSmartQuery(filter);
let virtualParameters = store.fetch("virtualParameters", filter, { const virtualParameters = store.fetch("virtualParameters", filter, {
limit: vnode.state.showCount || PAGE_SIZE, limit: vnode.state.showCount || PAGE_SIZE,
sort: sort sort: sort
}); });
let count = store.count("virtualParameters", filter); const count = store.count("virtualParameters", filter);
let selected = new Set(); const selected = new Set();
if (vnode.state.selected) if (vnode.state.selected) {
for (let virtualParameter of virtualParameters.value) for (const virtualParameter of virtualParameters.value) {
if (vnode.state.selected.has(virtualParameter["_id"])) if (vnode.state.selected.has(virtualParameter["_id"]))
selected.add(virtualParameter["_id"]); selected.add(virtualParameter["_id"]);
}
}
vnode.state.selected = selected; vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter); const downloadUrl = getDownloadUrl(vnode.attrs.filter);
+16 -13
View File
@@ -1,7 +1,5 @@
"use strict"; "use strict";
const expression = require("./expression");
const validators = { const validators = {
pass: require("./validators/pass"), pass: require("./validators/pass"),
test: require("./validators/test") test: require("./validators/test")
@@ -16,17 +14,18 @@ class Authorizer {
hasAccess(resourceType, access) { hasAccess(resourceType, access) {
const cacheKey = `${resourceType}-${access}`; const cacheKey = `${resourceType}-${access}`;
if (cacheKey in this.hasAccessCache) if (cacheKey in this.hasAccessCache) return this.hasAccessCache[cacheKey];
return this.hasAccessCache[cacheKey];
let has = false; let has = false;
for (let permissionSet of this.permissionSets) { for (const permissionSet of this.permissionSets) {
for (let perm of permissionSet) for (const perm of permissionSet) {
if (perm[resourceType]) if (perm[resourceType]) {
if (perm[resourceType].access >= access) { if (perm[resourceType].access >= access) {
has = true; has = true;
break; break;
} }
}
}
} }
this.hasAccessCache[cacheKey] = has; this.hasAccessCache[cacheKey] = has;
@@ -39,23 +38,26 @@ class Authorizer {
let funcs = {}; let funcs = {};
for (let permissionSet of this.permissionSets) for (const permissionSet of this.permissionSets) {
for (let perm of permissionSet) for (const perm of permissionSet) {
if (perm[resourceType]) { if (perm[resourceType]) {
if (perm[resourceType].access >= 3) { if (perm[resourceType].access >= 3) {
if (perm[resourceType].validate) if (perm[resourceType].validate) {
for (let [k, v] of Object.entries(perm[resourceType].validate)) for (const [k, v] of Object.entries(perm[resourceType].validate))
funcs[k] = v; funcs[k] = v;
}
} else { } else {
funcs = {}; funcs = {};
} }
} }
}
}
const validator = (mutationType, mutation, any) => { const validator = (mutationType, mutation, any) => {
let valid = false; let valid = false;
for (let [k, v] of Object.entries(funcs)) for (const [k, v] of Object.entries(funcs)) {
if (v) { if (v) {
let res = validators[k]( const res = validators[k](
resourceType, resourceType,
resource, resource,
mutationType, mutationType,
@@ -66,6 +68,7 @@ class Authorizer {
if (res > 0) valid = true; if (res > 0) valid = true;
else if (res < 0) return false; else if (res < 0) return false;
} }
}
return valid; return valid;
}; };
+9 -7
View File
@@ -7,7 +7,7 @@ function or(cnf1, cnf2) {
else if (cnf2.length === 0) return cnf1; else if (cnf2.length === 0) return cnf1;
if (cnf1.length > cnf2.length) { if (cnf1.length > cnf2.length) {
let c = cnf1; const c = cnf1;
cnf1 = cnf2; cnf1 = cnf2;
cnf2 = c; cnf2 = c;
} }
@@ -53,10 +53,11 @@ function booleanCnf(exp) {
return e; return e;
}); });
for (let [k, v] of ranges) for (const [k, v] of ranges) {
if (k.endsWith(":number")) if (k.endsWith(":number"))
ranges.set(k, Array.from(v).sort((a, b) => a - b)); ranges.set(k, Array.from(v).sort((a, b) => a - b));
else ranges.set(k, Array.from(v).sort()); else ranges.set(k, Array.from(v).sort());
}
const expressions = new Map(); const expressions = new Map();
const mutuallyExclusive = new Map(); const mutuallyExclusive = new Map();
@@ -113,11 +114,12 @@ function booleanCnf(exp) {
function getLikeVariables(lhs, pat, esc) { function getLikeVariables(lhs, pat, esc) {
const vars = []; const vars = [];
let pats = likePatterns.get(lhs); const pats = likePatterns.get(lhs);
pat = pats.get(`${esc || ""}:${pat}`); pat = pats.get(`${esc || ""}:${pat}`);
for (let p of pats.values()) for (const p of pats.values()) {
if (pat === p || likePatternIncludes(pat, p)) if (pat === p || likePatternIncludes(pat, p))
vars.push(`${lhs}:like:${JSON.stringify(p)}`); vars.push(`${lhs}:like:${JSON.stringify(p)}`);
}
return vars.map(key => { return vars.map(key => {
let f = expressions.get(key); let f = expressions.get(key);
@@ -185,7 +187,7 @@ function booleanCnf(exp) {
let rhs = JSON.stringify(clause[2]); let rhs = JSON.stringify(clause[2]);
// For consistency // For consistency
if (rhs > lhs) { if (rhs > lhs) {
let t = rhs; const t = rhs;
rhs = lhs; rhs = lhs;
lhs = t; lhs = t;
op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op; op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op;
@@ -218,8 +220,8 @@ function booleanCnf(exp) {
const cnf = recursive(exp, false); const cnf = recursive(exp, false);
for (let m of mutuallyExclusive.values()) { for (const m of mutuallyExclusive.values()) {
let ar = Array.from(m); const ar = Array.from(m);
for (let i = 0; i < ar.length; ++i) for (let i = 0; i < ar.length; ++i)
for (let j = i + 1; j < ar.length; ++j) cnf.push([0 - ar[i], 0 - ar[j]]); for (let j = i + 1; j < ar.length; ++j) cnf.push([0 - ar[i], 0 - ar[j]]);
} }
+16 -12
View File
@@ -4,7 +4,7 @@ const parsimmon = require("parsimmon");
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n"). // Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
function interpretEscapes(str) { function interpretEscapes(str) {
let escapes = { const escapes = {
b: "\b", b: "\b",
f: "\f", f: "\f",
n: "\n", n: "\n",
@@ -12,8 +12,8 @@ function interpretEscapes(str) {
t: "\t" t: "\t"
}; };
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => { return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
let type = escape.charAt(0); const type = escape.charAt(0);
let hex = escape.slice(1); const hex = escape.slice(1);
if (type === "u") return String.fromCharCode(parseInt(hex, 16)); if (type === "u") return String.fromCharCode(parseInt(hex, 16));
if (escapes.hasOwnProperty(type)) return escapes[type]; if (escapes.hasOwnProperty(type)) return escapes[type];
@@ -27,7 +27,7 @@ function map(exp, callback) {
let clone; let clone;
for (let i = 1; i < exp.length; ++i) { for (let i = 1; i < exp.length; ++i) {
let sub = map(exp[i], callback); const sub = map(exp[i], callback);
if (sub !== exp[i]) { if (sub !== exp[i]) {
clone = clone || exp.slice(); clone = clone || exp.slice();
clone[i] = sub; clone[i] = sub;
@@ -43,7 +43,7 @@ function binaryLeft(operatorsParser, nextParser) {
parsimmon.seq(operatorsParser, nextParser).many(), parsimmon.seq(operatorsParser, nextParser).many(),
(first, rest) => (first, rest) =>
rest.reduce((acc, ch) => { rest.reduce((acc, ch) => {
let [op, another] = ch; const [op, another] = ch;
if (Array.isArray(acc) && op === acc[0]) return acc.concat([another]); if (Array.isArray(acc) && op === acc[0]) return acc.concat([another]);
if (Array.isArray(another) && op === another[0]) if (Array.isArray(another) && op === another[0])
return [op, acc].concat(another.slice(1)); return [op, acc].concat(another.slice(1));
@@ -315,8 +315,9 @@ function stringify(exp, level = 0) {
.join(", ")})` .join(", ")})`
); );
} else if (op === "PARAM") { } else if (op === "PARAM") {
if (typeof exp[1] === "string") return wrap(exp[1]); if (typeof exp[1] === "string") {
else if (Array.isArray(exp[1]) && exp[1][0] === "||") return wrap(exp[1]);
} else if (Array.isArray(exp[1]) && exp[1][0] === "||") {
return wrap( return wrap(
exp[1] exp[1]
.slice(1) .slice(1)
@@ -326,24 +327,27 @@ function stringify(exp, level = 0) {
}) })
.join("") .join("")
); );
else return wrap(`{${stringify(exp[1])}}`); } else {
return wrap(`{${stringify(exp[1])}}`);
}
} else if (op === "IS NULL" || op === "IS NOT NULL") { } else if (op === "IS NULL" || op === "IS NOT NULL") {
return wrap(`${stringify(exp[1], opLevels[op])} ${op}`); return wrap(`${stringify(exp[1], opLevels[op])} ${op}`);
} else if (op === "LIKE" || op === "NOT LIKE") { } else if (op === "LIKE" || op === "NOT LIKE") {
if (exp[3]) if (exp[3]) {
return wrap( return wrap(
`${stringify(exp[1], opLevels[op])} ${op} ${stringify( `${stringify(exp[1], opLevels[op])} ${op} ${stringify(
exp[2], exp[2],
opLevels[op] opLevels[op]
)} ESCAPE ${stringify(exp[3], opLevels[op])}` )} ESCAPE ${stringify(exp[3], opLevels[op])}`
); );
else } else {
return wrap( return wrap(
`${stringify(exp[1], opLevels[op])} ${op} ${stringify( `${stringify(exp[1], opLevels[op])} ${op} ${stringify(
exp[2], exp[2],
opLevels[op] opLevels[op]
)}` )}`
); );
}
} else if (op in opLevels) { } else if (op in opLevels) {
const parts = exp.slice(1).map((e, i) => { const parts = exp.slice(1).map((e, i) => {
return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0)); return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0));
@@ -357,10 +361,10 @@ function stringify(exp, level = 0) {
} }
function parseLikePattern(pat, esc) { function parseLikePattern(pat, esc) {
let chars = pat.split(""); const chars = pat.split("");
for (let i = 0; i < chars.length; ++i) { for (let i = 0; i < chars.length; ++i) {
let c = chars[i]; const c = chars[i];
if (c === esc) { if (c === esc) {
chars[i] = chars[i + 1] || ""; chars[i] = chars[i + 1] || "";
chars[i + 1] = ""; chars[i + 1] = "";
+11 -14
View File
@@ -8,24 +8,14 @@ const isArray = Array.isArray;
const regExpCache = new WeakMap(); const regExpCache = new WeakMap();
function* permute(arr) { const REDUCE_SKIP = {};
if (arr.length <= 1) {
for (let i = 0; i < arr[0]; ++i) yield [i];
return;
}
let slc = arr.slice(1);
for (let i = 0; i < arr[0]; ++i)
for (let innerArr of permute(slc)) yield [i].concat(innerArr);
}
function reduce(exp, callback) { function reduce(exp, callback) {
let loop = true; let loop = true;
while (loop) { while (loop) {
loop = false; loop = false;
for (let i = 2; i < exp.length; ++i) { for (let i = 2; i < exp.length; ++i) {
let res = callback(exp[i - 1], exp[i]); const res = callback(exp[i - 1], exp[i]);
if (res !== undefined) { if (res !== REDUCE_SKIP) {
loop = true; loop = true;
exp = exp.slice(); exp = exp.slice();
exp.splice(i - 1, 2, res); exp.splice(i - 1, 2, res);
@@ -71,11 +61,13 @@ function evaluate(exp, obj, now, cb) {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
if (!isArray(a)) return a ? b : a; if (!isArray(a)) return a ? b : a;
if (!isArray(b)) return b ? a : b; if (!isArray(b)) return b ? a : b;
return REDUCE_SKIP;
}); });
} else if (e[0] === "OR") { } else if (e[0] === "OR") {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
if (!isArray(a)) return a ? a : b; if (!isArray(a)) return a ? a : b;
if (!isArray(b)) return b ? b : a; if (!isArray(b)) return b ? b : a;
return REDUCE_SKIP;
}); });
} else if (e[0] === "NOT") { } else if (e[0] === "NOT") {
if (!isArray(e[1])) return !e[1]; if (!isArray(e[1])) return !e[1];
@@ -131,6 +123,7 @@ function evaluate(exp, obj, now, cb) {
if (a == null || b == null) return null; if (a == null || b == null) return null;
return a * b; return a * b;
} }
return REDUCE_SKIP;
}); });
} else if (e[0] === "/") { } else if (e[0] === "/") {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
@@ -138,6 +131,7 @@ function evaluate(exp, obj, now, cb) {
if (a == null || b == null) return null; if (a == null || b == null) return null;
return a / b; return a / b;
} }
return REDUCE_SKIP;
}); });
} else if (e[0] === "+") { } else if (e[0] === "+") {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
@@ -145,6 +139,7 @@ function evaluate(exp, obj, now, cb) {
if (a == null || b == null) return null; if (a == null || b == null) return null;
return a + b; return a + b;
} }
return REDUCE_SKIP;
}); });
} else if (e[0] === "-") { } else if (e[0] === "-") {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
@@ -152,6 +147,7 @@ function evaluate(exp, obj, now, cb) {
if (a == null || b == null) return null; if (a == null || b == null) return null;
return a - b; return a - b;
} }
return REDUCE_SKIP;
}); });
} else if (e[0] === "||") { } else if (e[0] === "||") {
return reduce(e, (a, b) => { return reduce(e, (a, b) => {
@@ -159,6 +155,7 @@ function evaluate(exp, obj, now, cb) {
if (a == null || b == null) return null; if (a == null || b == null) return null;
return `${a}${b}`; return `${a}${b}`;
} }
return REDUCE_SKIP;
}); });
} }
return e; return e;
@@ -238,7 +235,7 @@ function likePatternToRegExp(pat, esc = "", flags = "") {
} }
function extractParams(exp) { function extractParams(exp) {
let params = []; const params = [];
expressionParser.map(exp, e => { expressionParser.map(exp, e => {
if (isArray(e) && e[0] === "PARAM") params.push(e[1]); if (isArray(e) && e[0] === "PARAM") params.push(e[1]);
return e; return e;
+4 -3
View File
@@ -33,11 +33,12 @@ function satisfiableAt(clauses, varVals, varnr, val) {
// find next unassigned var to split // find next unassigned var to split
let nextVar = 0; let nextVar = 0;
for (let i = 1; i < varVals.length; i++) for (let i = 1; i < varVals.length; i++) {
if (varVals[i] === 0) { if (varVals[i] === 0) {
nextVar = i; nextVar = i;
break; break;
} }
}
if (nextVar === 0) throw new Error("Error in satisfiableAt"); if (nextVar === 0) throw new Error("Error in satisfiableAt");
@@ -105,7 +106,7 @@ function unitPropagate(clauses, varVals) {
let unassignedLit = 0; // 0 means none found for this clause let unassignedLit = 0; // 0 means none found for this clause
for (let j = 0; j < clause.length; j++) { for (let j = 0; j < clause.length; j++) {
let lit = clause[j]; const lit = clause[j];
let nr, polarity; let nr, polarity;
if (lit < 0) { if (lit < 0) {
nr = 0 - lit; nr = 0 - lit;
@@ -149,7 +150,7 @@ function unitPropagate(clauses, varVals) {
// some units were derived: iterate the main loop // some units were derived: iterate the main loop
for (let j = 0; j < derivedQueue.length; j++) { for (let j = 0; j < derivedQueue.length; j++) {
let lit = derivedQueue[j]; const lit = derivedQueue[j];
let nr, polarity; let nr, polarity;
if (lit < 0) { if (lit < 0) {
nr = 0 - lit; nr = 0 - lit;
+51 -41
View File
@@ -6,7 +6,6 @@ const url = require("url");
const config = require("./config"); const config = require("./config");
const db = require("./db"); const db = require("./db");
const expression = require("../common/expression");
const mongodbFunctions = require("./mongodb-functions"); const mongodbFunctions = require("./mongodb-functions");
function filterToMongoQuery(filter, negate = false, res = {}) { function filterToMongoQuery(filter, negate = false, res = {}) {
@@ -25,7 +24,7 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
filterToMongoQuery(filter[1], !negate, res); filterToMongoQuery(filter[1], !negate, res);
} else if (op === "=") { } else if (op === "=") {
const param = filter[1][1]; const param = filter[1][1];
let p = (res[param] = res[param] || {}); const p = (res[param] = res[param] || {});
if (negate) p["$ne"] = filter[2]; if (negate) p["$ne"] = filter[2];
else p["$eq"] = filter[2]; else p["$eq"] = filter[2];
} else if (op === "<>") { } else if (op === "<>") {
@@ -69,15 +68,15 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
function deleteResource(resource, id) { function deleteResource(resource, id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
let options = url.parse( const options = url.parse(
`${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}` `${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}`
); );
options.method = "DELETE"; options.method = "DELETE";
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
_http _http
.request(options, res => { .request(options, res => {
@@ -91,13 +90,13 @@ function deleteResource(resource, id) {
} }
function postTask(task, connectionRequest, callback) { function postTask(task, connectionRequest, callback) {
let options = url.parse( const options = url.parse(
`${config.server.nbi}devices/${encodeURIComponent(task.device)}/tasks${ `${config.server.nbi}devices/${encodeURIComponent(task.device)}/tasks${
connectionRequest ? "?connection_request" : "" connectionRequest ? "?connection_request" : ""
}` }`
); );
options.method = "POST"; options.method = "POST";
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
_http _http
.request(options, res => { .request(options, res => {
@@ -114,14 +113,14 @@ function postTask(task, connectionRequest, callback) {
}); });
res.on("end", () => { res.on("end", () => {
let buf = new Buffer(bytes); const buf = Buffer.allocUnsafe(bytes);
let o = 0; let o = 0;
for (let c of chunks) { for (const c of chunks) {
c.copy(buf, o, 0, c.length); c.copy(buf, o, 0, c.length);
o += c.length; o += c.length;
} }
try { try {
let t = JSON.parse(buf); const t = JSON.parse(buf);
let connectionRequestStatus; let connectionRequestStatus;
if (connectionRequest) { if (connectionRequest) {
connectionRequestStatus = "OK"; connectionRequestStatus = "OK";
@@ -144,16 +143,16 @@ function postTask(task, connectionRequest, callback) {
function postTasks(deviceId, tasks) { function postTasks(deviceId, tasks) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let connectionRequestStatus; let connectionRequestStatus;
let promises = []; const promises = [];
for (let [idx, task] of tasks.entries()) { for (const [idx, task] of tasks.entries()) {
task.device = deviceId; task.device = deviceId;
promises.push( promises.push(
new Promise((res, rej) => { new Promise((res, rej) => {
let conReq = idx === tasks.length - 1; const conReq = idx === tasks.length - 1;
delete task._id; delete task._id;
task.expiry = 5; task.expiry = 5;
postTask(task, conReq, (err, t, crs) => { postTask(task, conReq, (err, t, crs) => {
if (err) return rej(err); if (err) return void rej(err);
if (conReq) connectionRequestStatus = crs; if (conReq) connectionRequestStatus = crs;
res({ _id: t._id, status: "pending" }); res({ _id: t._id, status: "pending" });
}); });
@@ -163,14 +162,15 @@ function postTasks(deviceId, tasks) {
Promise.all(promises) Promise.all(promises)
.then(statuses => { .then(statuses => {
if (connectionRequestStatus !== "OK") if (connectionRequestStatus !== "OK") {
return resolve({ return void resolve({
connectionRequest: connectionRequestStatus, connectionRequest: connectionRequestStatus,
tasks: statuses tasks: statuses
}); });
}
let promises2 = []; const promises2 = [];
for (let s of statuses) { for (const s of statuses) {
promises2.push(db.query("tasks", ["=", ["PARAM", "_id"], s._id])); promises2.push(db.query("tasks", ["=", ["PARAM", "_id"], s._id]));
promises2.push( promises2.push(
db.query("faults", [ db.query("faults", [
@@ -183,7 +183,7 @@ function postTasks(deviceId, tasks) {
Promise.all(promises2) Promise.all(promises2)
.then(res => { .then(res => {
for (let [i, r] of statuses.entries()) { for (const [i, r] of statuses.entries()) {
if (res[i * 2].length === 0) { if (res[i * 2].length === 0) {
r.status = "done"; r.status = "done";
} else if (res[i * 2 + 1].length === 1) { } else if (res[i * 2 + 1].length === 1) {
@@ -216,7 +216,7 @@ function updateTags(deviceId, tags) {
); );
if (onOff) options.method = "POST"; if (onOff) options.method = "POST";
else options.method = "DELETE"; else options.method = "DELETE";
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
_http _http
.request(options, res => { .request(options, res => {
@@ -237,17 +237,19 @@ function ping(host) {
`${config.server.nbi}ping/${encodeURIComponent(host)}` `${config.server.nbi}ping/${encodeURIComponent(host)}`
); );
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
_http _http
.request(options, res => { .request(options, res => {
if (res.statusCode === 404) { if (res.statusCode === 404) {
res.resume(); res.resume();
return resolve({}); return void resolve({});
} }
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
res.resume(); res.resume();
return reject(new Error(`Unexpected status code ${res.statusCode}`)); return void reject(
new Error(`Unexpected status code ${res.statusCode}`)
);
} }
const chunks = []; const chunks = [];
@@ -258,18 +260,19 @@ function ping(host) {
}); });
res.on("end", () => { res.on("end", () => {
let buf = new Buffer(bytes); const buf = Buffer.allocUnsafe(bytes);
let o = 0; let o = 0;
for (let c of chunks) { for (const c of chunks) {
c.copy(buf, o, 0, c.length); c.copy(buf, o, 0, c.length);
o += c.length; o += c.length;
} }
let m = buf const m = buf
.toString() .toString()
.match( .match(
/(\d) packets transmitted, (\d) received, ([\d.%]+) packet loss[^]*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/ /(\d) packets transmitted, (\d) received, ([\d.%]+) packet loss[^]*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/
); );
if (!m) return reject(new Error("Could not parse ping response")); if (!m)
return void reject(new Error("Could not parse ping response"));
resolve({ resolve({
packetsTransmitted: +m[1], packetsTransmitted: +m[1],
@@ -288,15 +291,15 @@ function ping(host) {
function putResource(resource, id, data) { function putResource(resource, id, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
let options = url.parse( const options = url.parse(
`${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}` `${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}`
); );
options.method = "PUT"; options.method = "PUT";
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
if (resource === "presets") data = mongodbFunctions.preProcessPreset(data); if (resource === "presets") data = mongodbFunctions.preProcessPreset(data);
@@ -306,7 +309,7 @@ function putResource(resource, id, data) {
if (resource === "virtualParameters") if (resource === "virtualParameters")
data = mongodbFunctions.preProcessVirtualParameters(data); data = mongodbFunctions.preProcessVirtualParameters(data);
let body = typeof data !== "string" ? JSON.stringify(data) : data; const body = typeof data !== "string" ? JSON.stringify(data) : data;
_http _http
.request(options, res => { .request(options, res => {
const chunks = []; const chunks = [];
@@ -317,14 +320,16 @@ function putResource(resource, id, data) {
}); });
res.on("end", () => { res.on("end", () => {
if (res.statusCode === 200) return resolve(); if (res.statusCode === 200) {
else if (res.statusCode !== 400) return void resolve();
return reject( } else if (res.statusCode !== 400) {
return void reject(
new Error(`Unexpected status code ${res.statusCode}`) new Error(`Unexpected status code ${res.statusCode}`)
); );
let buf = new Buffer(bytes); }
const buf = Buffer.allocUnsafe(bytes);
let o = 0; let o = 0;
for (let c of chunks) { for (const c of chunks) {
c.copy(buf, o, 0, c.length); c.copy(buf, o, 0, c.length);
o += c.length; o += c.length;
} }
@@ -337,17 +342,22 @@ function putResource(resource, id, data) {
function putFile(id, metadata, fileStream) { function putFile(id, metadata, fileStream) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let options = url.parse( const options = url.parse(
`${config.server.nbi}files/${encodeURIComponent(id)}` `${config.server.nbi}files/${encodeURIComponent(id)}`
); );
options.method = "PUT"; options.method = "PUT";
options.headers = Object.assign({}, metadata); options.headers = Object.assign({}, metadata);
let _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
let req = _http.request(options, res => { const req = _http.request(options, res => {
res.resume(); res.resume();
if (res.statusCode === 201) return resolve(); if (res.statusCode === 201) {
else return reject(new Error(`Unexpected status code ${res.statusCode}`)); return void resolve();
} else {
return void reject(
new Error(`Unexpected status code ${res.statusCode}`)
);
}
}); });
fileStream.pipe(req); fileStream.pipe(req);
}); });
+66 -62
View File
@@ -2,7 +2,6 @@
const zlib = require("zlib"); const zlib = require("zlib");
const Router = require("koa-router"); const Router = require("koa-router");
const config = require("./config");
const db = require("./db"); const db = require("./db");
const apiFunctions = require("./api-functions"); const apiFunctions = require("./api-functions");
const expression = require("../common/expression"); const expression = require("../common/expression");
@@ -38,7 +37,7 @@ const resources = {
tasks: 0 tasks: 0
}; };
for (let [resource, flags] of Object.entries(resources)) { for (const [resource, flags] of Object.entries(resources)) {
router.head(`/${resource}`, async (ctx, next) => { router.head(`/${resource}`, async (ctx, next) => {
let filter = true; let filter = true;
if (ctx.request.query.filter) if (ctx.request.query.filter)
@@ -52,17 +51,18 @@ for (let [resource, flags] of Object.entries(resources)) {
if (!ctx.state.authorizer.hasAccess(resource, 1)) { if (!ctx.state.authorizer.hasAccess(resource, 1)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
// Exclude temporary tasks and faults // Exclude temporary tasks and faults
if (resource === "tasks" || resource === "faults") if (resource === "tasks" || resource === "faults") {
filter = expression.and(filter, [ filter = expression.and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000]
]); ]);
}
let count = await db.count(resource, filter); const count = await db.count(resource, filter);
ctx.set("X-Total-Count", count); ctx.set("X-Total-Count", count);
ctx.body = ""; ctx.body = "";
@@ -72,7 +72,7 @@ for (let [resource, flags] of Object.entries(resources)) {
}); });
router.get(`/${resource}`, async (ctx, next) => { router.get(`/${resource}`, async (ctx, next) => {
let options = {}; const options = {};
let filter = true; let filter = true;
if (ctx.request.query.filter) if (ctx.request.query.filter)
filter = expression.parse(ctx.request.query.filter); filter = expression.parse(ctx.request.query.filter);
@@ -80,10 +80,11 @@ for (let [resource, flags] of Object.entries(resources)) {
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip; if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
if (ctx.request.query.sort) if (ctx.request.query.sort)
options.sort = JSON.parse(ctx.request.query.sort); options.sort = JSON.parse(ctx.request.query.sort);
if (ctx.request.query.projection) if (ctx.request.query.projection) {
options.projection = ctx.request.query.projection options.projection = ctx.request.query.projection
.split(",") .split(",")
.reduce((obj, k) => Object.assign(obj, { [k]: 1 }), {}); .reduce((obj, k) => Object.assign(obj, { [k]: 1 }), {});
}
const log = { const log = {
message: `Query ${resource}`, message: `Query ${resource}`,
@@ -97,15 +98,16 @@ for (let [resource, flags] of Object.entries(resources)) {
if (!ctx.state.authorizer.hasAccess(resource, 2)) { if (!ctx.state.authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
// Exclude temporary tasks and faults // Exclude temporary tasks and faults
if (resource === "tasks" || resource === "faults") if (resource === "tasks" || resource === "faults") {
filter = expression.and(filter, [ filter = expression.and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000]
]); ]);
}
let stream; let stream;
switch (ctx.acceptsEncodings("gzip", "deflate", "identity")) { switch (ctx.acceptsEncodings("gzip", "deflate", "identity")) {
@@ -139,7 +141,7 @@ for (let [resource, flags] of Object.entries(resources)) {
// CSV download // CSV download
router.get(`/${resource}.csv`, async (ctx, next) => { router.get(`/${resource}.csv`, async (ctx, next) => {
let options = { projection: {} }; const options = { projection: {} };
let filter = true; let filter = true;
if (ctx.request.query.filter) if (ctx.request.query.filter)
filter = expression.parse(ctx.request.query.filter); filter = expression.parse(ctx.request.query.filter);
@@ -159,28 +161,25 @@ for (let [resource, flags] of Object.entries(resources)) {
if (!ctx.state.authorizer.hasAccess(resource, 2)) { if (!ctx.state.authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
const columns = JSON.parse(ctx.request.query.columns); const columns = JSON.parse(ctx.request.query.columns);
const now = Date.now(); const now = Date.now();
for (let [k, v] of Object.entries(columns)) { for (const [k, v] of Object.entries(columns)) {
const e = expression.evaluate( const e = expression.evaluate(expression.parse(v), null, now);
expression.parse(v),
null,
now
);
columns[k] = e; columns[k] = e;
for (let p of expression.extractParams(e)) options.projection[p] = 1; for (const p of expression.extractParams(e)) options.projection[p] = 1;
} }
// Exclude temporary tasks and faults // Exclude temporary tasks and faults
if (resource === "tasks" || resource === "faults") if (resource === "tasks" || resource === "faults") {
filter = expression.and(filter, [ filter = expression.and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000]
]); ]);
}
let stream; let stream;
switch (ctx.acceptsEncodings("gzip", "deflate", "identity")) { switch (ctx.acceptsEncodings("gzip", "deflate", "identity")) {
@@ -209,24 +208,26 @@ for (let [resource, flags] of Object.entries(resources)) {
Object.keys(columns).map(k => `"${k.replace(/"/, '""')}"`) + "\n" Object.keys(columns).map(k => `"${k.replace(/"/, '""')}"`) + "\n"
); );
await db.query(resource, filter, options, obj => { await db.query(resource, filter, options, obj => {
let arr = Object.values(columns).map(exp => { const arr = Object.values(columns).map(exp => {
let v = expression.evaluate(exp, obj, null, e => { const v = expression.evaluate(exp, obj, null, e => {
if (Array.isArray(e)) if (Array.isArray(e)) {
if (e[0] === "PARAM") { if (e[0] === "PARAM") {
if (resource === "devices") { if (resource === "devices") {
if (e[1] === "Tags") { if (e[1] === "Tags") {
let tags = []; const tags = [];
for (let p in obj) for (const p in obj)
if (p.startsWith("Tags.")) tags.push(p.slice(5)); if (p.startsWith("Tags.")) tags.push(p.slice(5));
return tags.join(", "); return tags.join(", ");
} }
} }
} else if (e[0] === "FUNC") { } else if (e[0] === "FUNC") {
if (e[1] === "DATE_STRING") if (e[1] === "DATE_STRING") {
if (e[2] && !Array.isArray(e[2])) if (e[2] && !Array.isArray(e[2]))
return new Date(e[2]).toISOString(); return new Date(e[2]).toISOString();
}
} }
}
return e; return e;
}); });
@@ -249,15 +250,15 @@ for (let [resource, flags] of Object.entries(resources)) {
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"` filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`
}; };
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id]; const filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
if (!ctx.state.authorizer.hasAccess(resource, 2)) { if (!ctx.state.authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let res = await db.query(resource, filter); const res = await db.query(resource, filter);
if (!res.length) return next(); if (!res.length) return void next();
logger.accessInfo(log); logger.accessInfo(log);
ctx.body = ""; ctx.body = "";
@@ -270,22 +271,22 @@ for (let [resource, flags] of Object.entries(resources)) {
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"` filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`
}; };
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id]; const filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
if (!ctx.state.authorizer.hasAccess(resource, 2)) { if (!ctx.state.authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let res = await db.query(resource, filter); const res = await db.query(resource, filter);
if (!res.length) return next(); if (!res.length) return void next();
logger.accessInfo(log); logger.accessInfo(log);
ctx.body = res[0]; ctx.body = res[0];
}); });
// TODO add PUT, PATCH routes // TODO add PUT, PATCH routes
if (flags & RESOURCE_DELETE) if (flags & RESOURCE_DELETE) {
router.delete(`/${resource}/:id`, async (ctx, next) => { router.delete(`/${resource}/:id`, async (ctx, next) => {
const log = { const log = {
message: `Delete ${resource}`, message: `Delete ${resource}`,
@@ -294,18 +295,18 @@ for (let [resource, flags] of Object.entries(resources)) {
}; };
const authorizer = ctx.state.authorizer; const authorizer = ctx.state.authorizer;
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id]; const filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
if (!authorizer.hasAccess(resource, 2)) { if (!authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let res = await db.query(resource, filter); const res = await db.query(resource, filter);
if (!res.length) return next(); if (!res.length) return void next();
const validate = authorizer.getValidator(resource, res[0]); const validate = authorizer.getValidator(resource, res[0]);
if (!validate("delete")) { if (!validate("delete")) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return (ctx.status = 403); return void (ctx.status = 403);
} }
await apiFunctions.deleteResource(resource, ctx.params.id); await apiFunctions.deleteResource(resource, ctx.params.id);
@@ -314,8 +315,9 @@ for (let [resource, flags] of Object.entries(resources)) {
ctx.body = ""; ctx.body = "";
}); });
}
if (flags & RESOURCE_PUT) if (flags & RESOURCE_PUT) {
router.put(`/${resource}/:id`, async (ctx, next) => { router.put(`/${resource}/:id`, async (ctx, next) => {
const id = ctx.params.id; const id = ctx.params.id;
@@ -328,30 +330,31 @@ for (let [resource, flags] of Object.entries(resources)) {
const authorizer = ctx.state.authorizer; const authorizer = ctx.state.authorizer;
if (!authorizer.hasAccess(resource, 3)) { if (!authorizer.hasAccess(resource, 3)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let obj = ctx.request.body; const obj = ctx.request.body;
const validate = authorizer.getValidator(resource, obj); const validate = authorizer.getValidator(resource, obj);
if (!validate("put")) { if (!validate("put")) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return (ctx.status = 403); return void (ctx.status = 403);
} }
let err = await apiFunctions.putResource(resource, id, obj); const err = await apiFunctions.putResource(resource, id, obj);
if (err) { if (err) {
log.message += " failed"; log.message += " failed";
logger.accessWarn(log); logger.accessWarn(log);
ctx.body = err; ctx.body = err;
return (ctx.status = 400); return void (ctx.status = 400);
} }
logger.accessInfo(log); logger.accessInfo(log);
ctx.body = ""; ctx.body = "";
}); });
}
} }
router.put("/files/:id", async (ctx, next) => { router.put("/files/:id", async (ctx, next) => {
@@ -367,10 +370,10 @@ router.put("/files/:id", async (ctx, next) => {
const authorizer = ctx.state.authorizer; const authorizer = ctx.state.authorizer;
if (!authorizer.hasAccess(resource, 3)) { if (!authorizer.hasAccess(resource, 3)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let metadata = { const metadata = {
fileType: ctx.request.headers["metadata.filetype"] || "", fileType: ctx.request.headers["metadata.filetype"] || "",
oui: ctx.request.headers["metadata.oui"] || "", oui: ctx.request.headers["metadata.oui"] || "",
productClass: ctx.request.headers["metadata.productclass"] || "", productClass: ctx.request.headers["metadata.productclass"] || "",
@@ -380,16 +383,16 @@ router.put("/files/:id", async (ctx, next) => {
const validate = authorizer.getValidator(resource, metadata); const validate = authorizer.getValidator(resource, metadata);
if (!validate("put")) { if (!validate("put")) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return (ctx.status = 403); return void (ctx.status = 403);
} }
let err = await apiFunctions.putFile(id, metadata, ctx.req); const err = await apiFunctions.putFile(id, metadata, ctx.req);
if (err) { if (err) {
log.message += " failed"; log.message += " failed";
logger.accessWarn(log); logger.accessWarn(log);
ctx.body = err; ctx.body = err;
return (ctx.status = 400); return void (ctx.status = 400);
} }
log.metadata = metadata; log.metadata = metadata;
@@ -406,22 +409,23 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
}; };
const authorizer = ctx.state.authorizer; const authorizer = ctx.state.authorizer;
let filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id]; const filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id];
if (!authorizer.hasAccess("devices", 2)) { if (!authorizer.hasAccess("devices", 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let devices = await db.query("devices", filter); const devices = await db.query("devices", filter);
if (!devices.length) return next(); if (!devices.length) return void next();
const validate = authorizer.getValidator("devices", devices[0]); const validate = authorizer.getValidator("devices", devices[0]);
for (let t of ctx.request.body) for (const t of ctx.request.body) {
if (!validate("task", t)) { if (!validate("task", t)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return (ctx.status = 403); return void (ctx.status = 403);
} }
}
let res = await apiFunctions.postTasks(ctx.params.id, ctx.request.body); const res = await apiFunctions.postTasks(ctx.params.id, ctx.request.body);
log.tasks = res.tasks.map(t => t._id).join(","); log.tasks = res.tasks.map(t => t._id).join(",");
@@ -440,18 +444,18 @@ router.post("/devices/:id/tags", async (ctx, next) => {
}; };
const authorizer = ctx.state.authorizer; const authorizer = ctx.state.authorizer;
let filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id]; const filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id];
if (!authorizer.hasAccess("devices", 2)) { if (!authorizer.hasAccess("devices", 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return next(); return void next();
} }
let res = await db.query("devices", filter); const res = await db.query("devices", filter);
if (!res.length) return next(); if (!res.length) return void next();
const validate = authorizer.getValidator("devices", res[0]); const validate = authorizer.getValidator("devices", res[0]);
if (!validate("tags", ctx.request.body)) { if (!validate("tags", ctx.request.body)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
return (ctx.status = 403); return void (ctx.status = 403);
} }
await apiFunctions.updateTags(ctx.params.id, ctx.request.body); await apiFunctions.updateTags(ctx.params.id, ctx.request.body);
+1 -3
View File
@@ -3,7 +3,7 @@
const fs = require("fs"); const fs = require("fs");
const configFile = JSON.parse(fs.readFileSync("./config.json")); const configFile = JSON.parse(fs.readFileSync("./config.json"));
let config; const config = loadConfig(configFile);
function getClientConfig() { function getClientConfig() {
return Object.assign({}, config, { return Object.assign({}, config, {
@@ -33,6 +33,4 @@ function loadConfig(conf, root = {}) {
return root; return root;
} }
config = loadConfig(configFile);
module.exports = Object.assign({}, config, { getClientConfig }); module.exports = Object.assign({}, config, { getClientConfig });
+10 -6
View File
@@ -22,18 +22,19 @@ const RESOURCE_COLLECTION = {
}; };
function getClient() { function getClient() {
if (!_clientPromise) if (!_clientPromise) {
_clientPromise = new Promise((resolve, reject) => { _clientPromise = new Promise((resolve, reject) => {
const CONNECTION_URL = config.server.mongodbConnectionUrl; const CONNECTION_URL = config.server.mongodbConnectionUrl;
mongodb.MongoClient.connect( mongodb.MongoClient.connect(
CONNECTION_URL, CONNECTION_URL,
{ useNewUrlParser: true }, { useNewUrlParser: true },
(err, client) => { (err, client) => {
if (err) return reject(err); if (err) return void reject(err);
resolve(client); resolve(client);
} }
); );
}); });
}
return _clientPromise; return _clientPromise;
} }
@@ -61,12 +62,13 @@ function query(resource, filter, options, callback) {
.db(RESOURCE_DB[resource]) .db(RESOURCE_DB[resource])
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
const cursor = collection.find(q); const cursor = collection.find(q);
if (options.projection) if (options.projection) {
cursor.project( cursor.project(
resource === "devices" resource === "devices"
? mongodbFunctions.processDeviceProjection(options.projection) ? mongodbFunctions.processDeviceProjection(options.projection)
: options.projection : options.projection
); );
}
if (options.skip) cursor.skip(options.skip); if (options.skip) cursor.skip(options.skip);
if (options.limit) cursor.limit(options.limit); if (options.limit) cursor.limit(options.limit);
@@ -83,7 +85,7 @@ function query(resource, filter, options, callback) {
cursor.sort(s); cursor.sort(s);
} }
if (!callback) if (!callback) {
cursor.toArray((err, docs) => { cursor.toArray((err, docs) => {
if (err) return reject(err); if (err) return reject(err);
if (resource === "devices") if (resource === "devices")
@@ -98,7 +100,7 @@ function query(resource, filter, options, callback) {
docs = docs.map(d => mongodbFunctions.flattenFile(d)); docs = docs.map(d => mongodbFunctions.flattenFile(d));
return resolve(docs); return resolve(docs);
}); });
else } else {
cursor.forEach( cursor.forEach(
doc => { doc => {
if (resource === "devices") if (resource === "devices")
@@ -118,6 +120,7 @@ function query(resource, filter, options, callback) {
else resolve(); else resolve();
} }
); );
}
}); });
}); });
} }
@@ -152,10 +155,11 @@ function count(resource, filter) {
} }
function disconnect() { function disconnect() {
if (_clientPromise) if (_clientPromise) {
_clientPromise.then(client => { _clientPromise.then(client => {
client.close(); client.close();
}); });
}
} }
exports.query = query; exports.query = query;
+4 -3
View File
@@ -153,9 +153,10 @@ function formatSimple(details, systemd) {
flatten(details); flatten(details);
let remote = ""; let remote = "";
if (details.remoteAddress) if (details.remoteAddress) {
if (details.user) remote = `${details.user}@${details.remoteAddress}: `; if (details.user) remote = `${details.user}@${details.remoteAddress}: `;
else remote = `${details.remoteAddress}: `; else remote = `${details.remoteAddress}: `;
}
const keys = Object.keys(details); const keys = Object.keys(details);
const skip = { const skip = {
@@ -168,8 +169,8 @@ function formatSimple(details, systemd) {
let meta = ""; let meta = "";
let kv = []; const kv = [];
for (let k of keys) for (const k of keys)
if (!skip[k]) kv.push(`${k}=${JSON.stringify(details[k])}`); if (!skip[k]) kv.push(`${k}=${JSON.stringify(details[k])}`);
if (kv.length) meta = `; ${kv.join(" ")}`; if (kv.length) meta = `; ${kv.join(" ")}`;
+37 -28
View File
@@ -44,7 +44,7 @@ function processDeviceFilter(filter) {
}); });
if (typeof e[2] === "number") { if (typeof e[2] === "number") {
let alt = e.slice(); const alt = e.slice();
alt[2] = new Date(e[2]); alt[2] = new Date(e[2]);
e = ["OR", e, alt]; e = ["OR", e, alt];
} }
@@ -58,7 +58,7 @@ function processTasksFilter(filter) {
return expressionParser.map(filter, exp => { return expressionParser.map(filter, exp => {
if (!Array.isArray(exp)) return exp; if (!Array.isArray(exp)) return exp;
if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) { if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) {
let e = exp.slice(); const e = exp.slice();
if (e[1][0] === "PARAM" && e[1][1] === "_id") if (e[1][0] === "PARAM" && e[1][1] === "_id")
e[2] = new mongodb.ObjectID(e[2]); e[2] = new mongodb.ObjectID(e[2]);
else if (e[1][0] === "PARAM" && e[1][1] === "timestamp") else if (e[1][0] === "PARAM" && e[1][1] === "timestamp")
@@ -75,7 +75,7 @@ function processFaultsFilter(filter) {
return expressionParser.map(filter, exp => { return expressionParser.map(filter, exp => {
if (!Array.isArray(exp)) return exp; if (!Array.isArray(exp)) return exp;
if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) { if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) {
let e = exp.slice(); const e = exp.slice();
if (e[1][0] === "PARAM" && e[1][1] === "timestamp") e[2] = new Date(e[2]); if (e[1][0] === "PARAM" && e[1][1] === "timestamp") e[2] = new Date(e[2]);
else if (e[1][0] === "PARAM" && e[1][1] === "expiry") else if (e[1][0] === "PARAM" && e[1][1] === "expiry")
e[2] = new Date(e[2]); e[2] = new Date(e[2]);
@@ -101,7 +101,7 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
filterToMongoQuery(filter[1], !negate, res); filterToMongoQuery(filter[1], !negate, res);
} else if (op === "=") { } else if (op === "=") {
const param = filter[1][1]; const param = filter[1][1];
let p = (res[param] = res[param] || {}); const p = (res[param] = res[param] || {});
if (negate) p["$ne"] = filter[2]; if (negate) p["$ne"] = filter[2];
else p["$eq"] = filter[2]; else p["$eq"] = filter[2];
} else if (op === "<>") { } else if (op === "<>") {
@@ -141,17 +141,19 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
let param; let param;
let flags; let flags;
if (filter[1][0] === "FUNC" && filter[1][1] === "UPPER") { if (filter[1][0] === "FUNC" && filter[1][1] === "UPPER") {
if (filter[2] !== filter[2].toUpperCase()) if (filter[2] !== filter[2].toUpperCase()) {
throw new Error( throw new Error(
"Cannot compare UPPER() against non upper case pattern" "Cannot compare UPPER() against non upper case pattern"
); );
}
param = filter[1][2][1]; param = filter[1][2][1];
flags = "i"; flags = "i";
} else if (filter[1][0] === "FUNC" && filter[1][1] === "LOWER") { } else if (filter[1][0] === "FUNC" && filter[1][1] === "LOWER") {
if (filter[2] !== filter[2].toLowerCase()) if (filter[2] !== filter[2].toLowerCase()) {
throw new Error( throw new Error(
"Cannot compare LOWER() against non lower case pattern" "Cannot compare LOWER() against non lower case pattern"
); );
}
param = filter[1][2][1]; param = filter[1][2][1];
flags = "i"; flags = "i";
} else { } else {
@@ -171,7 +173,7 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
function processDeviceProjection(projection) { function processDeviceProjection(projection) {
if (!projection) return projection; if (!projection) return projection;
const p = {}; const p = {};
for (const [k, v] of Object.entries(projection)) for (const [k, v] of Object.entries(projection)) {
if (k === "DeviceID.ID") { if (k === "DeviceID.ID") {
p["_id"] = 1; p["_id"] = 1;
} else if (k.startsWith("DeviceID")) { } else if (k.startsWith("DeviceID")) {
@@ -189,6 +191,7 @@ function processDeviceProjection(projection) {
} else { } else {
p[k] = v; p[k] = v;
} }
}
return p; return p;
} }
@@ -196,18 +199,19 @@ function processDeviceProjection(projection) {
function processDeviceSort(sort) { function processDeviceSort(sort) {
if (!sort) return sort; if (!sort) return sort;
const s = {}; const s = {};
for (const [k, v] of Object.entries(sort)) for (const [k, v] of Object.entries(sort)) {
if (k === "DeviceID.ID") s["_id"] = v; if (k === "DeviceID.ID") s["_id"] = v;
else if (k === "Events.Inform") s["_lastInform"] = v; else if (k === "Events.Inform") s["_lastInform"] = v;
else s[k] = v; else s[k] = v;
}
return s; return s;
} }
function flattenDevice(device) { function flattenDevice(device) {
function recursive(input, root, output, timestamp) { function recursive(input, root, output, timestamp) {
for (let [name, tree] of Object.entries(input)) { for (const [name, tree] of Object.entries(input)) {
if (!root) if (!root) {
if (name === "_lastInform") { if (name === "_lastInform") {
output["Events.Inform"] = { output["Events.Inform"] = {
value: [Date.parse(tree), "xsd:dateTime"], value: [Date.parse(tree), "xsd:dateTime"],
@@ -294,7 +298,7 @@ function flattenDevice(device) {
objectTimestamp: timestamp objectTimestamp: timestamp
}; };
for (let t of tree) for (const t of tree) {
output[`Tags.${t}`] = { output[`Tags.${t}`] = {
value: [true, "xsd:boolean"], value: [true, "xsd:boolean"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
@@ -303,7 +307,9 @@ function flattenDevice(device) {
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp
}; };
}
} }
}
if (name.startsWith("_")) continue; if (name.startsWith("_")) continue;
@@ -329,7 +335,7 @@ function flattenDevice(device) {
attrs["writableTimestamp"] = childrenTimestamp; attrs["writableTimestamp"] = childrenTimestamp;
} }
let r = root ? `${root}.${name}` : name; const r = root ? `${root}.${name}` : name;
output[r] = attrs; output[r] = attrs;
if (attrs["object"] || tree["object"] == null) if (attrs["object"] || tree["object"] == null)
@@ -348,14 +354,14 @@ function flattenFault(fault) {
if (f.timestamp) f.timestamp = +f.timestamp; if (f.timestamp) f.timestamp = +f.timestamp;
if (f.expiry) f.expiry = +f.expiry; if (f.expiry) f.expiry = +f.expiry;
if (f.detail) { if (f.detail) {
for (let [k, v] of Object.entries(f.detail)) fault[`detail.${k}`] = v; for (const [k, v] of Object.entries(f.detail)) fault[`detail.${k}`] = v;
delete f.detail; delete f.detail;
} }
return f; return f;
} }
function flattenTask(task) { function flattenTask(task) {
let t = Object.assign({}, task); const t = Object.assign({}, task);
t._id = "" + t._id; t._id = "" + t._id;
if (t.timestamp) t.timestamp = +t.timestamp; if (t.timestamp) t.timestamp = +t.timestamp;
if (t.expiry) t.expiry = +t.expiry; if (t.expiry) t.expiry = +t.expiry;
@@ -363,20 +369,21 @@ function flattenTask(task) {
} }
function mongoQueryToFilter(query) { function mongoQueryToFilter(query) {
let expressions = []; const expressions = [];
function recursive(_query) { function recursive(_query) {
for (let [k, v] of Object.entries(_query)) for (const [k, v] of Object.entries(_query)) {
if (k[0] == "$") { if (k[0] == "$") {
if (k == "$and") for (let vv of Object.values(v)) recursive(vv); if (k == "$and") for (const vv of Object.values(v)) recursive(vv);
else throw new Error(`Operator ${k} not supported`); else throw new Error(`Operator ${k} not supported`);
} else if (k == "_tags") { } else if (k == "_tags") {
if (typeof v === "object") { if (typeof v === "object") {
if (Array.isArray(v)) throw new Error(`Invalid type`); if (Array.isArray(v)) throw new Error(`Invalid type`);
for (let [kk, vv] of Object.entries(v)) for (const [kk, vv] of Object.entries(v)) {
if (kk == "$ne") if (kk == "$ne")
expressions.push(["IS NULL", ["PARAM", `Tags.${vv}`]]); expressions.push(["IS NULL", ["PARAM", `Tags.${vv}`]]);
else throw new Error(`Operator ${kk} not supported`); else throw new Error(`Operator ${kk} not supported`);
}
} else { } else {
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]); expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]);
} }
@@ -388,7 +395,7 @@ function mongoQueryToFilter(query) {
} else if (typeof v === "object") { } else if (typeof v === "object") {
if (Array.isArray(v)) throw new Error(`Invalid type`); if (Array.isArray(v)) throw new Error(`Invalid type`);
for (let [kk, vv] of Object.entries(v)) { for (const [kk, vv] of Object.entries(v)) {
let op; let op;
switch (kk) { switch (kk) {
case "$eq": case "$eq":
@@ -417,6 +424,7 @@ function mongoQueryToFilter(query) {
} else { } else {
expressions.push(["=", ["PARAM", k], v]); expressions.push(["=", ["PARAM", k], v]);
} }
}
} }
recursive(query); recursive(query);
@@ -428,7 +436,7 @@ function mongoQueryToFilter(query) {
} }
function flattenPreset(preset) { function flattenPreset(preset) {
let p = Object.assign({}, preset); const p = Object.assign({}, preset);
if (p.precondition) { if (p.precondition) {
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition)); p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
p.precondition = p.precondition.length p.precondition = p.precondition.length
@@ -437,12 +445,12 @@ function flattenPreset(preset) {
} }
if (p.events) { if (p.events) {
let e = []; const e = [];
for (let [k, v] of Object.entries(p.events)) e.push(v ? k : `-${k}`); for (const [k, v] of Object.entries(p.events)) e.push(v ? k : `-${k}`);
p.events = e.join(", "); p.events = e.join(", ");
} }
let provision = p.configurations[0]; const provision = p.configurations[0];
if ( if (
p.configurations.length != 1 || p.configurations.length != 1 ||
provision.type != "provision" || provision.type != "provision" ||
@@ -459,7 +467,7 @@ function flattenPreset(preset) {
} }
function flattenFile(file) { function flattenFile(file) {
let f = {}; const f = {};
f["_id"] = file["_id"]; f["_id"] = file["_id"];
if (file.metadata) { if (file.metadata) {
f["metadata.fileType"] = file.metadata.fileType || ""; f["metadata.fileType"] = file.metadata.fileType || "";
@@ -471,14 +479,14 @@ function flattenFile(file) {
} }
function preProcessPreset(data) { function preProcessPreset(data) {
let preset = Object.assign({}, data); const preset = Object.assign({}, data);
preset.precondition = preset.precondition preset.precondition = preset.precondition
? filterToMongoQuery(expression.parse(preset.precondition)) ? filterToMongoQuery(expression.parse(preset.precondition))
: {}; : {};
preset.precondition = JSON.stringify(preset.precondition); preset.precondition = JSON.stringify(preset.precondition);
let events = {}; const events = {};
if (preset.events) if (preset.events) {
for (let e of preset.events.split(",")) { for (let e of preset.events.split(",")) {
let v = true; let v = true;
e = e.trim(); e = e.trim();
@@ -488,12 +496,13 @@ function preProcessPreset(data) {
} }
if (e) events[e] = v; if (e) events[e] = v;
} }
}
preset.events = events; preset.events = events;
if (!preset.provision) throw new Error("Invalid preset provision"); if (!preset.provision) throw new Error("Invalid preset provision");
let configuration = { const configuration = {
type: "provision", type: "provision",
name: preset.provision name: preset.provision
}; };
+11 -11
View File
@@ -42,11 +42,10 @@ function getPermissionSets(roles) {
"virtualParameters", "virtualParameters",
"files" "files"
].includes(k) ].includes(k)
) ) {
if (v.filter == null || v.filter === "") v.filter = true; if (v.filter == null || v.filter === "") v.filter = true;
else { else v.filter = expression.parse(v.filter);
v.filter = expression.parse(v.filter); }
}
} }
return p; return p;
}); });
@@ -76,11 +75,13 @@ koa.use(
); );
koa.use(async (ctx, next) => { koa.use(async (ctx, next) => {
if (ctx.state.user && ctx.state.user.roles) if (ctx.state.user && ctx.state.user.roles) {
ctx.state.authorizer = new Authorizer( ctx.state.authorizer = new Authorizer(
getPermissionSets(ctx.state.user.roles) getPermissionSets(ctx.state.user.roles)
); );
else ctx.state.authorizer = new Authorizer([]); } else {
ctx.state.authorizer = new Authorizer([]);
}
return next(); return next();
}); });
@@ -89,7 +90,7 @@ router.post("/login", async ctx => {
const username = ctx.request.body.username; const username = ctx.request.body.username;
const password = ctx.request.body.password; const password = ctx.request.body.password;
let log = { const log = {
message: "Log in", message: "Log in",
context: ctx, context: ctx,
username: username username: username
@@ -110,10 +111,9 @@ router.post("/login", async ctx => {
logger.accessWarn(log); logger.accessWarn(log);
} }
let roles; const roles = await authSimple(username, password);
roles = await authSimple(username, password);
if (roles) return success(roles, "simple"); if (roles) return void success(roles, "simple");
failure(); failure();
}); });
@@ -172,7 +172,7 @@ koa.use(
koa.use(router.routes()); koa.use(router.routes());
koa.use(koaStatic("./public")); koa.use(koaStatic("./public"));
let server = http const server = http
.createServer(koa.callback()) .createServer(koa.callback())
.listen(config.server.port, config.server.interface); .listen(config.server.port, config.server.interface);