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