From 16b10a698b6b9bf0ea9103ff31a71ccd238f902d Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Sun, 23 Dec 2018 03:30:31 -0800 Subject: [PATCH] Implement tags --- client/components.js | 2 + client/components/tags.js | 76 +++++++++++++++++++++++++ client/css/app.css | 16 ++++++ client/store.js | 9 +++ config.json | 113 ++++++++++++++++++++------------------ server/api-functions.js | 28 ++++++++++ server/api.js | 5 ++ 7 files changed, 195 insertions(+), 54 deletions(-) create mode 100644 client/components/tags.js diff --git a/client/components.js b/client/components.js index 16c30e5..bfcbc13 100644 --- a/client/components.js +++ b/client/components.js @@ -9,6 +9,7 @@ import summonButton from "./components/summon-button"; import deviceFaults from "./components/device-faults"; import allParameters from "./components/all-parameters"; import deviceActions from "./components/device-actions"; +import tags from "./components/tags"; function get(name) { if (name === "parameter") return parameter; @@ -20,6 +21,7 @@ function get(name) { else if (name === "device-faults") return deviceFaults; else if (name === "all-parameters") return allParameters; else if (name === "device-actions") return deviceActions; + else if (name === "tags") return tags; else throw new Error(`No such component '${name}'`); } diff --git a/client/components/tags.js b/client/components/tags.js new file mode 100644 index 0000000..6ba3750 --- /dev/null +++ b/client/components/tags.js @@ -0,0 +1,76 @@ +"use strict"; + +import m from "mithril"; +import * as notifications from "../notifications"; +import * as store from "../store"; + +const component = { + view: vnode => { + const device = vnode.attrs.device; + let writable = true; + if (vnode.attrs.writable != null) writable = vnode.attrs.writable; + + const tags = []; + for (let p of Object.keys(device)) + if (p.startsWith("Tags.")) tags.push(p.slice(5)); + + tags.sort(); + + if (!writable) return m(".tags", tags.map(t => m("span.tag", t))); + + return m( + ".tags", + tags.map(tag => + m( + "span.tag", + tag, + m( + "button", + { + onclick: e => { + e.target.disabled = true; + store + .updateTags(device["DeviceID.ID"].value[0], { [tag]: false }) + .then(() => { + e.target.disabled = false; + notifications.push("success", `Tag '${tag}' unassigned`); + store.fulfill(0, Date.now()); + }) + .catch(err => notifications.push("error", err.message)); + } + }, + "✕" + ) + ) + ), + m( + "span.tag.writable", + m( + "button", + { + onclick: e => { + e.target.disabled = true; + const tag = prompt(`Enter tag to assign to device:`); + if (!tag) { + e.target.disabled = false; + return; + } + + store + .updateTags(device["DeviceID.ID"].value[0], { [tag]: true }) + .then(() => { + e.target.disabled = false; + notifications.push("success", `Tag '${tag}' assigned`); + store.fulfill(0, Date.now()); + }) + .catch(err => notifications.push("error", err.message)); + } + }, + "🞢" + ) + ) + ); + } +}; + +export default component; diff --git a/client/css/app.css b/client/css/app.css index 0da3c05..07be138 100644 --- a/client/css/app.css +++ b/client/css/app.css @@ -496,3 +496,19 @@ span.na { margin-right: 10px; } } + +span.tag { + display: inline-block; + background-image: url('data:image/svg+xml;utf-8,'); + background-size: cover; + background-repeat: no-repeat; + border-radius: 0.2em; + padding-left: 1em; + padding-right: 0.2em; +} + +.tags { + & > .tag { + margin-right: 0.5em; + } +} diff --git a/client/store.js b/client/store.js index c9adbf1..33e2493 100644 --- a/client/store.js +++ b/client/store.js @@ -363,6 +363,14 @@ function postTasks(tasks, callback) { }); } +function updateTags(deviceId, tags) { + return m.request({ + method: "POST", + url: `/api/devices/${encodeURIComponent(deviceId)}/tags`, + data: tags + }); +} + function deleteResource(resourceType, id) { return m.request({ method: "DELETE", @@ -393,6 +401,7 @@ export { unpackFilter, getTimestamp, postTasks, + updateTags, deleteResource, evaluateExpression }; diff --git a/config.json b/config.json index ac6c194..593b541 100644 --- a/config.json +++ b/config.json @@ -30,6 +30,10 @@ "ui.index.6.components.1.type": "parameter", "ui.index.6.components.2.type": "overview-dot", "ui.index.6.components.2.chart": "online", + "ui.index.7.label": "Tags", + "ui.index.7.parameter": "Tags", + "ui.index.7.type": "tags", + "ui.index.7.writable": false, "ui.overview.groups.online.label": "", "ui.overview.groups.online.charts.1": "online", "ui.overview.charts.online.label": "Online status", @@ -42,58 +46,59 @@ "ui.overview.charts.online.slices.others.label": "Others", "ui.overview.charts.online.slices.others.color": "#f7fcb9", "ui.overview.charts.online.slices.others.filter": "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)", - "ui.device.1.type": "parameter-list", - "ui.device.1.parameters.1.type": "container", - "ui.device.1.parameters.1.label": "Last inform", - "ui.device.1.parameters.1.element": "span.inform", - "ui.device.1.parameters.1.parameter": "Events.Inform", - "ui.device.1.parameters.1.components.1.type": "parameter", - "ui.device.1.parameters.1.components.2.type": "overview-dot", - "ui.device.1.parameters.1.components.2.chart": "online", - "ui.device.1.parameters.1.components.3.type": "summon-button", - "ui.device.1.parameters.1.components.3.parameters.1": "InternetGatewayDevice.DeviceInfo.HardwareVersion", - "ui.device.1.parameters.1.components.3.parameters.2": "InternetGatewayDevice.DeviceInfo.SoftwareVersion", - "ui.device.1.parameters.1.components.3.parameters.3": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress", - "ui.device.1.parameters.1.components.3.parameters.4": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress", - "ui.device.1.parameters.1.components.3.parameters.5": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", - "ui.device.1.parameters.1.components.3.parameters.6": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", - "ui.device.1.parameters.1.components.3.parameters.7": "InternetGatewayDevice.LANDevice.1.Hosts.Host", - "ui.device.1.parameters.2.label": "Last boot", - "ui.device.1.parameters.2.parameter": "Events.1_BOOT", - "ui.device.1.parameters.3.label": "Last bootstrap", - "ui.device.1.parameters.3.parameter": "Events.0_BOOTSTRAP", - "ui.device.1.parameters.4.label": "Registered", - "ui.device.1.parameters.4.parameter": "Events.Registered", - "ui.device.1.parameters.2.label": "Serial number", - "ui.device.1.parameters.2.parameter": "InternetGatewayDevice.DeviceInfo.SerialNumber", - "ui.device.1.parameters.3.label": "Product class", - "ui.device.1.parameters.3.parameter": "InternetGatewayDevice.DeviceInfo.ProductClass", - "ui.device.1.parameters.4.label": "OUI", - "ui.device.1.parameters.4.parameter": "InternetGatewayDevice.DeviceInfo.ManufacturerOUI", - "ui.device.1.parameters.5.label": "Manufacturer", - "ui.device.1.parameters.5.parameter": "InternetGatewayDevice.DeviceInfo.Manufacturer", - "ui.device.1.parameters.6.label": "Hardware version", - "ui.device.1.parameters.6.parameter": "InternetGatewayDevice.DeviceInfo.HardwareVersion", - "ui.device.1.parameters.7.label": "Software version", - "ui.device.1.parameters.7.parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion", - "ui.device.1.parameters.8.label": "MAC", - "ui.device.1.parameters.8.parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress", - "ui.device.1.parameters.9.label": "IP", - "ui.device.1.parameters.9.parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress", - "ui.device.1.parameters.10.label": "WLAN SSID", - "ui.device.1.parameters.10.parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", - "ui.device.1.parameters.11.label": "WLAN passphrase", - "ui.device.1.parameters.11.parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", - "ui.device.2.type": "parameter-table", - "ui.device.2.label": "WLAN hosts", - "ui.device.2.object": "InternetGatewayDevice.LANDevice.1.Hosts.Host", - "ui.device.2.parameters.1.label": "Host name", - "ui.device.2.parameters.1.parameter": "HostName", - "ui.device.2.parameters.2.label": "IP address", - "ui.device.2.parameters.2.parameter": "IPAddress", - "ui.device.2.parameters.3.label": "MAC address", - "ui.device.2.parameters.3.parameter": "MACAddress", - "ui.device.3.type": "device-faults", - "ui.device.4.type": "all-parameters", - "ui.device.5.type": "device-actions" + "ui.device.10.type": "tags", + "ui.device.30.type": "parameter-list", + "ui.device.30.parameters.1.type": "container", + "ui.device.30.parameters.1.label": "Last inform", + "ui.device.30.parameters.1.element": "span.inform", + "ui.device.30.parameters.1.parameter": "Events.Inform", + "ui.device.30.parameters.1.components.1.type": "parameter", + "ui.device.30.parameters.1.components.2.type": "overview-dot", + "ui.device.30.parameters.1.components.2.chart": "online", + "ui.device.30.parameters.1.components.3.type": "summon-button", + "ui.device.30.parameters.1.components.3.parameters.1": "InternetGatewayDevice.DeviceInfo.HardwareVersion", + "ui.device.30.parameters.1.components.3.parameters.2": "InternetGatewayDevice.DeviceInfo.SoftwareVersion", + "ui.device.30.parameters.1.components.3.parameters.3": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress", + "ui.device.30.parameters.1.components.3.parameters.4": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress", + "ui.device.30.parameters.1.components.3.parameters.5": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", + "ui.device.30.parameters.1.components.3.parameters.6": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", + "ui.device.30.parameters.1.components.3.parameters.7": "InternetGatewayDevice.LANDevice.1.Hosts.Host", + "ui.device.30.parameters.2.label": "Last boot", + "ui.device.30.parameters.2.parameter": "Events.1_BOOT", + "ui.device.30.parameters.3.label": "Last bootstrap", + "ui.device.30.parameters.3.parameter": "Events.0_BOOTSTRAP", + "ui.device.30.parameters.4.label": "Registered", + "ui.device.30.parameters.4.parameter": "Events.Registered", + "ui.device.30.parameters.2.label": "Serial number", + "ui.device.30.parameters.2.parameter": "InternetGatewayDevice.DeviceInfo.SerialNumber", + "ui.device.30.parameters.3.label": "Product class", + "ui.device.30.parameters.3.parameter": "InternetGatewayDevice.DeviceInfo.ProductClass", + "ui.device.30.parameters.4.label": "OUI", + "ui.device.30.parameters.4.parameter": "InternetGatewayDevice.DeviceInfo.ManufacturerOUI", + "ui.device.30.parameters.5.label": "Manufacturer", + "ui.device.30.parameters.5.parameter": "InternetGatewayDevice.DeviceInfo.Manufacturer", + "ui.device.30.parameters.6.label": "Hardware version", + "ui.device.30.parameters.6.parameter": "InternetGatewayDevice.DeviceInfo.HardwareVersion", + "ui.device.30.parameters.7.label": "Software version", + "ui.device.30.parameters.7.parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion", + "ui.device.30.parameters.8.label": "MAC", + "ui.device.30.parameters.8.parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress", + "ui.device.30.parameters.9.label": "IP", + "ui.device.30.parameters.9.parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress", + "ui.device.30.parameters.10.label": "WLAN SSID", + "ui.device.30.parameters.10.parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", + "ui.device.30.parameters.11.label": "WLAN passphrase", + "ui.device.30.parameters.11.parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", + "ui.device.40.type": "parameter-table", + "ui.device.40.label": "WLAN hosts", + "ui.device.40.object": "InternetGatewayDevice.LANDevice.1.Hosts.Host", + "ui.device.40.parameters.1.label": "Host name", + "ui.device.40.parameters.1.parameter": "HostName", + "ui.device.40.parameters.2.label": "IP address", + "ui.device.40.parameters.2.parameter": "IPAddress", + "ui.device.40.parameters.3.label": "MAC address", + "ui.device.40.parameters.3.parameter": "MACAddress", + "ui.device.50.type": "device-faults", + "ui.device.60.type": "all-parameters", + "ui.device.70.type": "device-actions" } diff --git a/server/api-functions.js b/server/api-functions.js index 7e066ba..52ff915 100644 --- a/server/api-functions.js +++ b/server/api-functions.js @@ -340,7 +340,35 @@ function postTasks(deviceId, tasks) { }); } +function updateTags(deviceId, tags) { + return Promise.all( + Object.entries(tags).map( + ([tag, onOff]) => + new Promise((resolve, reject) => { + const options = url.parse( + `${config.get( + "server.nbi" + )}devices/${deviceId}/tags/${encodeURIComponent(tag)}` + ); + if (onOff) options.method = "POST"; + else options.method = "DELETE"; + let _http = options.protocol === "https:" ? https : http; + + _http + .request(options, res => { + res.resume(); + if (res.statusCode === 200) resolve(); + else + reject(new Error(`Unexpected status code ${res.statusCode}`)); + }) + .end(); + }) + ) + ); +} + exports.query = query; exports.count = count; exports.postTasks = postTasks; exports.deleteResource = deleteResource; +exports.updateTags = updateTags; diff --git a/server/api.js b/server/api.js index 3b99eb6..043b5fb 100644 --- a/server/api.js +++ b/server/api.js @@ -86,4 +86,9 @@ router.post("/devices/:id/tasks", async ctx => { ctx.body = res.tasks; }); +router.post("/devices/:id/tags", async ctx => { + await apiFunctions.updateTags(ctx.params.id, ctx.request.body); + ctx.body = ""; +}); + module.exports = router;