Refactor components
Implement context mechanism to pass default attributes to descendant components.
This commit is contained in:
1 parent
d46d133878
commit
ee8c43747e
35 files changed
+1903
-1831
No files matched your search
+118
-120
@@ -1,9 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import * as components from "../components";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import longTextComponent from "../long-text-component";
|
||||
import { parse } from "../../lib/common/expression-parser";
|
||||
import memoize from "../../lib/common/memoize";
|
||||
|
||||
@@ -13,125 +11,125 @@ function escapeRegExp(str) {
|
||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||
}
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
const search = m("input", {
|
||||
type: "text",
|
||||
placeholder: "Search parameters",
|
||||
oninput: e => {
|
||||
vnode.state.searchString = e.target.value;
|
||||
e.redraw = false;
|
||||
clearTimeout(vnode.state.timeout);
|
||||
vnode.state.timeout = setTimeout(m.redraw, 500);
|
||||
}
|
||||
});
|
||||
|
||||
const instanceRegex = /\.[0-9]+$/;
|
||||
let re;
|
||||
if (vnode.state.searchString) {
|
||||
const keywords = vnode.state.searchString.split(" ").filter(s => s);
|
||||
if (keywords.length)
|
||||
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
|
||||
}
|
||||
|
||||
const rows = Object.keys(device)
|
||||
.sort()
|
||||
.map(k => {
|
||||
const p = device[k];
|
||||
const val = [];
|
||||
const attrs = {};
|
||||
if (re) {
|
||||
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
||||
if (!re.test(str)) attrs.style = "display: none;";
|
||||
const search = m("input", {
|
||||
type: "text",
|
||||
placeholder: "Search parameters",
|
||||
oninput: e => {
|
||||
vnode.state.searchString = e.target.value;
|
||||
e.redraw = false;
|
||||
clearTimeout(vnode.state.timeout);
|
||||
vnode.state.timeout = setTimeout(m.redraw, 500);
|
||||
}
|
||||
|
||||
if (p.object === false) {
|
||||
val.push(
|
||||
m(
|
||||
components.get("parameter"),
|
||||
Object.assign({ device: device, parameter: memoizedParse(k) })
|
||||
)
|
||||
);
|
||||
} else if (p.object && p.writable) {
|
||||
if (instanceRegex.test(k)) {
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Delete this instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Create a new instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Refresh tree",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "getParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
parameterNames: [k]
|
||||
});
|
||||
}
|
||||
},
|
||||
"↺"
|
||||
)
|
||||
);
|
||||
|
||||
return m(
|
||||
"tr",
|
||||
attrs,
|
||||
m("td.left", m(longTextComponent, { text: k })),
|
||||
m("td.right", val)
|
||||
);
|
||||
});
|
||||
|
||||
return m(
|
||||
".all-parameters",
|
||||
m(
|
||||
"a.download-csv",
|
||||
{
|
||||
href: `/api/devices/${device["DeviceID.ID"].value[0]}.csv`,
|
||||
download: "",
|
||||
style: "float: right;"
|
||||
},
|
||||
"Download"
|
||||
),
|
||||
search,
|
||||
m(".parameter-list", m("table", m("tbody", rows)))
|
||||
);
|
||||
}
|
||||
};
|
||||
const instanceRegex = /\.[0-9]+$/;
|
||||
let re;
|
||||
if (vnode.state.searchString) {
|
||||
const keywords = vnode.state.searchString.split(" ").filter(s => s);
|
||||
if (keywords.length)
|
||||
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
|
||||
}
|
||||
|
||||
export default component;
|
||||
const rows = Object.keys(device)
|
||||
.sort()
|
||||
.map(k => {
|
||||
const p = device[k];
|
||||
const val = [];
|
||||
const attrs = {};
|
||||
if (re) {
|
||||
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
||||
if (!re.test(str)) attrs.style = "display: none;";
|
||||
}
|
||||
|
||||
if (p.object === false) {
|
||||
val.push(
|
||||
m(
|
||||
"parameter",
|
||||
Object.assign({ device: device, parameter: memoizedParse(k) })
|
||||
)
|
||||
);
|
||||
} else if (p.object && p.writable) {
|
||||
if (instanceRegex.test(k)) {
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Delete this instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Create a new instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
val.push(
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Refresh tree",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "getParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
parameterNames: [k]
|
||||
});
|
||||
}
|
||||
},
|
||||
"↺"
|
||||
)
|
||||
);
|
||||
|
||||
return m(
|
||||
"tr",
|
||||
attrs,
|
||||
m("td.left", m("long-text", { text: k })),
|
||||
m("td.right", val)
|
||||
);
|
||||
});
|
||||
|
||||
return m(
|
||||
".all-parameters",
|
||||
m(
|
||||
"a.download-csv",
|
||||
{
|
||||
href: `/api/devices/${device["DeviceID.ID"].value[0]}.csv`,
|
||||
download: "",
|
||||
style: "float: right;"
|
||||
},
|
||||
"Download"
|
||||
),
|
||||
search,
|
||||
m(".parameter-list", m("table", m("tbody", rows)))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+17
-19
@@ -1,24 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import * as components from "../components";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
if (vnode.attrs.filter) {
|
||||
if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
|
||||
return null;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
if (vnode.attrs.filter) {
|
||||
if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = Object.values(vnode.attrs.components).map(c => {
|
||||
if (typeof c !== "object") return `${c}`;
|
||||
return m(c.type, c);
|
||||
});
|
||||
if (vnode.attrs.element) return m(vnode.attrs.element, children);
|
||||
else return children;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
if (vnode.attrs.element) return m(vnode.attrs.element, children);
|
||||
else return children;
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
@@ -1,90 +1,90 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as notifications from "../notifications";
|
||||
import * as store from "../store";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
const buttons = [];
|
||||
const buttons = [];
|
||||
|
||||
buttons.push(
|
||||
m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Reboot device",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "reboot",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Reboot"
|
||||
)
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
m(
|
||||
"button.critical",
|
||||
{
|
||||
title: "Factory reset device",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "factoryReset",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Reset"
|
||||
)
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
m(
|
||||
"button.critical",
|
||||
{
|
||||
title: "Push a firmware or a config file",
|
||||
onclick: () => {
|
||||
taskQueue.stageDownload({
|
||||
name: "download",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Push file"
|
||||
)
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Delete device",
|
||||
onclick: () => {
|
||||
if (!confirm("Deleting this device. Are you sure?")) return;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
|
||||
store
|
||||
.deleteResource("devices", deviceId)
|
||||
.then(() => {
|
||||
notifications.push("success", `${deviceId}: Device deleted`);
|
||||
m.route.set("/devices");
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
buttons.push(
|
||||
m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Reboot device",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "reboot",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Delete"
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
"Reboot"
|
||||
)
|
||||
);
|
||||
|
||||
return m(".actions-bar", buttons);
|
||||
}
|
||||
};
|
||||
buttons.push(
|
||||
m(
|
||||
"button.critical",
|
||||
{
|
||||
title: "Factory reset device",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "factoryReset",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Reset"
|
||||
)
|
||||
);
|
||||
|
||||
export default component;
|
||||
buttons.push(
|
||||
m(
|
||||
"button.critical",
|
||||
{
|
||||
title: "Push a firmware or a config file",
|
||||
onclick: () => {
|
||||
taskQueue.stageDownload({
|
||||
name: "download",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
"Push file"
|
||||
)
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Delete device",
|
||||
onclick: () => {
|
||||
if (!confirm("Deleting this device. Are you sure?")) return;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
|
||||
store
|
||||
.deleteResource("devices", deviceId)
|
||||
.then(() => {
|
||||
notifications.push("success", `${deviceId}: Device deleted`);
|
||||
m.route.set("/devices");
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
"Delete"
|
||||
)
|
||||
);
|
||||
|
||||
return m(".actions-bar", buttons);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,71 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
import * as notifications from "../notifications";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const faults = store.fetch("faults", [
|
||||
"AND",
|
||||
[">", ["PARAM", "_id"], `${deviceId}:`],
|
||||
["<", ["PARAM", "_id"], `${deviceId}:zzzz`]
|
||||
]);
|
||||
|
||||
const headers = ["Channel", "Code", "Message", "Retries", "Timestamp"].map(
|
||||
l => m("th", l)
|
||||
);
|
||||
const thead = m("thead", m("tr", headers));
|
||||
|
||||
const rows = [];
|
||||
for (const f of faults.value) {
|
||||
rows.push([
|
||||
m("td", f["channel"]),
|
||||
m("td", f["code"]),
|
||||
m("td", f["message"]),
|
||||
m("td", f["retries"]),
|
||||
m("td", new Date(f["timestamp"]).toLocaleString()),
|
||||
m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Delete fault",
|
||||
onclick: e => {
|
||||
e.redraw = false;
|
||||
store
|
||||
.deleteResource("faults", f["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Fault deleted");
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
m.redraw();
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
)
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const faults = store.fetch("faults", [
|
||||
"AND",
|
||||
[">", ["PARAM", "_id"], `${deviceId}:`],
|
||||
["<", ["PARAM", "_id"], `${deviceId}:zzzz`]
|
||||
]);
|
||||
|
||||
const headers = [
|
||||
"Channel",
|
||||
"Code",
|
||||
"Message",
|
||||
"Retries",
|
||||
"Timestamp"
|
||||
].map(l => m("th", l));
|
||||
const thead = m("thead", m("tr", headers));
|
||||
|
||||
const rows = [];
|
||||
for (const f of faults.value) {
|
||||
rows.push([
|
||||
m("td", f["channel"]),
|
||||
m("td", f["code"]),
|
||||
m("td", f["message"]),
|
||||
m("td", f["retries"]),
|
||||
m("td", new Date(f["timestamp"]).toLocaleString()),
|
||||
m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Delete fault",
|
||||
onclick: e => {
|
||||
e.redraw = false;
|
||||
store
|
||||
.deleteResource("faults", f["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Fault deleted");
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
m.redraw();
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
let tbody;
|
||||
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);
|
||||
}
|
||||
|
||||
let tbody;
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import * as components from "../components";
|
||||
import { m } from "../components";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
let deviceId;
|
||||
if (vnode.attrs.device)
|
||||
deviceId = vnode.attrs.device["DeviceID.ID"].value[0];
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
let deviceId;
|
||||
if (vnode.attrs.device)
|
||||
deviceId = vnode.attrs.device["DeviceID.ID"].value[0];
|
||||
|
||||
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);
|
||||
});
|
||||
if (deviceId) {
|
||||
return m(
|
||||
"a",
|
||||
{ href: `#!/devices/${encodeURIComponent(deviceId)}` },
|
||||
children
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
const children = Object.values(vnode.attrs.components).map(c => {
|
||||
if (typeof c !== "object") return `${c}`;
|
||||
const attrs = Object.assign({}, vnode.attrs, c);
|
||||
return m(attrs.type, attrs);
|
||||
});
|
||||
if (deviceId) {
|
||||
return m(
|
||||
"a",
|
||||
{ href: `#!/devices/${encodeURIComponent(deviceId)}` },
|
||||
children
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
@@ -1,38 +1,38 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import config from "../config";
|
||||
import * as store from "../store";
|
||||
|
||||
const CHARTS = config.ui.overview.charts;
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const chart = CHARTS[vnode.attrs.chart];
|
||||
for (const slice of Object.values(chart.slices)) {
|
||||
const filter = slice.filter;
|
||||
if (store.evaluateExpression(filter, device)) {
|
||||
const dot = m(
|
||||
"svg",
|
||||
{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
},
|
||||
m("circle", {
|
||||
cx: "0.5em",
|
||||
cy: "0.5em",
|
||||
r: "0.4em",
|
||||
fill: slice.color
|
||||
})
|
||||
);
|
||||
return m("span.overview-dot", dot, `${slice.label}`);
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const chart = CHARTS[vnode.attrs.chart];
|
||||
for (const slice of Object.values(chart.slices)) {
|
||||
const filter = slice.filter;
|
||||
if (store.evaluateExpression(filter, device)) {
|
||||
const dot = m(
|
||||
"svg",
|
||||
{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
},
|
||||
m("circle", {
|
||||
cx: "0.5em",
|
||||
cy: "0.5em",
|
||||
r: "0.4em",
|
||||
fill: slice.color
|
||||
})
|
||||
);
|
||||
return m("span.overview-dot", dot, `${slice.label}`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
@@ -1,35 +1,35 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { get } from "../components";
|
||||
import memoize from "../../lib/common/memoize";
|
||||
import { m } from "../components";
|
||||
|
||||
const getChildAttrs = memoize((attrs, device) =>
|
||||
Object.assign({}, attrs, { device: device })
|
||||
);
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
export default {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const rows = Object.values(vnode.attrs.parameters).map(parameter => {
|
||||
const p = m.context(
|
||||
{
|
||||
device: device,
|
||||
parameter: parameter.parameter
|
||||
},
|
||||
parameter.type || "parameter",
|
||||
parameter
|
||||
);
|
||||
|
||||
const rows = Object.values(vnode.attrs.parameters).map(parameter => {
|
||||
const p = m(
|
||||
get(parameter.type || "parameter"),
|
||||
getChildAttrs(parameter, device)
|
||||
);
|
||||
return m(
|
||||
"tr",
|
||||
{
|
||||
onupdate: vn => {
|
||||
vn.dom.style.display = p.dom ? "" : "none";
|
||||
}
|
||||
},
|
||||
m("th", parameter.label),
|
||||
m("td", p)
|
||||
);
|
||||
});
|
||||
|
||||
return m(
|
||||
"tr",
|
||||
{
|
||||
onupdate: vn => {
|
||||
vn.dom.style.display = p.dom ? "" : "none";
|
||||
}
|
||||
},
|
||||
m("th", parameter.label),
|
||||
m("td", p)
|
||||
);
|
||||
});
|
||||
|
||||
return m("table.parameter-list", rows);
|
||||
}
|
||||
};
|
||||
return m("table.parameter-list", rows);
|
||||
}
|
||||
};
|
||||
}
|
||||
+108
-106
@@ -1,132 +1,134 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import * as components from "../components";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
import * as expressionParser from "../../lib/common/expression-parser";
|
||||
|
||||
const component = {
|
||||
oninit: vnode => {
|
||||
const obj = vnode.attrs.parameter;
|
||||
if (!Array.isArray(obj) || !obj[0] === "PARAM")
|
||||
throw new Error("Object must be a parameter path");
|
||||
vnode.state.object = obj[1];
|
||||
vnode.state.parameters = Object.values(vnode.attrs.childParameters);
|
||||
},
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const object = store.evaluateExpression(vnode.state.object, device);
|
||||
const parameters = vnode.state.parameters;
|
||||
export default function component() {
|
||||
return {
|
||||
oninit: vnode => {
|
||||
const obj = vnode.attrs.parameter;
|
||||
if (!Array.isArray(obj) || !obj[0] === "PARAM")
|
||||
throw new Error("Object must be a parameter path");
|
||||
vnode.state.object = obj[1];
|
||||
vnode.state.parameters = Object.values(vnode.attrs.childParameters);
|
||||
},
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const object = store.evaluateExpression(vnode.state.object, device);
|
||||
const parameters = vnode.state.parameters;
|
||||
|
||||
if (!device[object]) return null;
|
||||
if (!device[object]) return null;
|
||||
|
||||
const instances = new Set();
|
||||
const prefix = `${object}.`;
|
||||
for (const p in device) {
|
||||
if (p.startsWith(prefix)) {
|
||||
const i = p.indexOf(".", prefix.length);
|
||||
if (i === -1) instances.add(p);
|
||||
else instances.add(p.slice(0, i));
|
||||
const instances = new Set();
|
||||
const prefix = `${object}.`;
|
||||
for (const p in device) {
|
||||
if (p.startsWith(prefix)) {
|
||||
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 headers = Object.values(parameters).map(p => m("th", p.label));
|
||||
|
||||
const thead = m("thead", m("tr", headers));
|
||||
const thead = m("thead", m("tr", headers));
|
||||
|
||||
const rows = [];
|
||||
for (const i of instances) {
|
||||
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
|
||||
const rows = [];
|
||||
for (const i of instances) {
|
||||
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
|
||||
|
||||
filter = expressionParser.map(filter, e => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
return ["PARAM", ["||", i, ".", e[1]]];
|
||||
return e;
|
||||
});
|
||||
|
||||
if (!store.evaluateExpression(filter, device)) continue;
|
||||
|
||||
const row = parameters.map(p => {
|
||||
const param = expressionParser.map(p.parameter, e => {
|
||||
filter = expressionParser.map(filter, e => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
return ["PARAM", ["||", i, ".", e[1]]];
|
||||
return e;
|
||||
});
|
||||
return m(
|
||||
"td",
|
||||
m(
|
||||
components.get(p.type || "parameter"),
|
||||
Object.assign({}, p, {
|
||||
device: device,
|
||||
parameter: param,
|
||||
label: ""
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if (device[i].writable === true) {
|
||||
row.push(
|
||||
m(
|
||||
if (!store.evaluateExpression(filter, device)) continue;
|
||||
|
||||
const row = parameters.map(p => {
|
||||
const param = expressionParser.map(p.parameter, e => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
return ["PARAM", ["||", i, ".", e[1]]];
|
||||
return e;
|
||||
});
|
||||
return m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
m.context(
|
||||
{
|
||||
title: "Delete this instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: i
|
||||
});
|
||||
}
|
||||
device: device,
|
||||
parameter: param
|
||||
},
|
||||
"✕"
|
||||
p.type || "parameter",
|
||||
Object.assign({}, p, {
|
||||
device: device,
|
||||
parameter: param
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if (device[i].writable === true) {
|
||||
row.push(
|
||||
m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Delete this instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: i
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
rows.push(m("tr", row));
|
||||
}
|
||||
|
||||
if (!rows.length) {
|
||||
rows.push(
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
|
||||
);
|
||||
}
|
||||
|
||||
if (device[object].writable === true) {
|
||||
rows.push(
|
||||
m(
|
||||
"tr",
|
||||
m("td", { colspan: headers.length }),
|
||||
m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Create a new instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: object
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
rows.push(m("tr", row));
|
||||
|
||||
let label;
|
||||
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
|
||||
|
||||
return [label, m("table.table", thead, m("tbody", rows))];
|
||||
}
|
||||
|
||||
if (!rows.length) {
|
||||
rows.push(
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
|
||||
);
|
||||
}
|
||||
|
||||
if (device[object].writable === true) {
|
||||
rows.push(
|
||||
m(
|
||||
"tr",
|
||||
m("td", { colspan: headers.length }),
|
||||
m(
|
||||
"td",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
title: "Create a new instance",
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: object
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let label;
|
||||
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
|
||||
|
||||
return [label, m("table.table", thead, m("tbody", rows))];
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
+52
-53
@@ -1,12 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
import * as expression from "../../lib/common/expression";
|
||||
import memoize from "../../lib/common/memoize";
|
||||
import timeAgo from "../timeago";
|
||||
import longTextComponent from "../long-text-component";
|
||||
|
||||
const evaluateParam = memoize((exp, obj, now) => {
|
||||
let timestamp = now;
|
||||
@@ -32,62 +31,62 @@ const evaluateParam = memoize((exp, obj, now) => {
|
||||
return { value, timestamp, parameter };
|
||||
});
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
const { value, timestamp, parameter } = evaluateParam(
|
||||
vnode.attrs.parameter,
|
||||
vnode.attrs.device,
|
||||
store.getTimestamp()
|
||||
);
|
||||
const { value, timestamp, parameter } = evaluateParam(
|
||||
vnode.attrs.parameter,
|
||||
vnode.attrs.device,
|
||||
store.getTimestamp()
|
||||
);
|
||||
|
||||
if (value == null) return null;
|
||||
if (value == null) return null;
|
||||
|
||||
let edit;
|
||||
if (device[parameter] && device[parameter].writable) {
|
||||
edit = m(
|
||||
"button",
|
||||
{
|
||||
title: "Edit parameter value",
|
||||
onclick: () => {
|
||||
taskQueue.stageSpv({
|
||||
name: "setParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
parameterValues: [
|
||||
[
|
||||
parameter,
|
||||
device[parameter].value[0],
|
||||
device[parameter].value[1]
|
||||
let edit;
|
||||
if (device[parameter] && device[parameter].writable) {
|
||||
edit = m(
|
||||
"button",
|
||||
{
|
||||
title: "Edit parameter value",
|
||||
onclick: () => {
|
||||
taskQueue.stageSpv({
|
||||
name: "setParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
parameterValues: [
|
||||
[
|
||||
parameter,
|
||||
device[parameter].value[0],
|
||||
device[parameter].value[1]
|
||||
]
|
||||
]
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
"✎"
|
||||
);
|
||||
}
|
||||
|
||||
const el = m("long-text", { text: `${value}` });
|
||||
|
||||
return m(
|
||||
"span",
|
||||
{
|
||||
class: "parameter-value",
|
||||
onmouseover: e => {
|
||||
e.redraw = false;
|
||||
// Don't update any child element
|
||||
if (e.target === el.dom) {
|
||||
const now = Date.now();
|
||||
const localeString = new Date(timestamp).toLocaleString();
|
||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
"✎"
|
||||
el,
|
||||
edit
|
||||
);
|
||||
}
|
||||
|
||||
const el = m(longTextComponent, { text: `${value}` });
|
||||
|
||||
return m(
|
||||
"span",
|
||||
{
|
||||
class: "parameter-value",
|
||||
onmouseover: e => {
|
||||
e.redraw = false;
|
||||
// Don't update any child element
|
||||
if (e.target === el.dom) {
|
||||
const now = Date.now();
|
||||
const localeString = new Date(timestamp).toLocaleString();
|
||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
el,
|
||||
edit
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
+45
-45
@@ -1,56 +1,56 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
const REFRESH_INTERVAL = 3000;
|
||||
|
||||
const component = {
|
||||
oninit: vnode => {
|
||||
vnode.state.timestamp = 0;
|
||||
vnode.state.ping = null;
|
||||
vnode.state.host = null;
|
||||
vnode.state.timeout = null;
|
||||
},
|
||||
view: vnode => {
|
||||
const refresh = () => {
|
||||
const device = vnode.attrs.device;
|
||||
let param =
|
||||
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
||||
if (!param)
|
||||
param = device["Device.ManagementServer.ConnectionRequestURL"];
|
||||
if (!param || !param.value) return;
|
||||
export default function component() {
|
||||
return {
|
||||
oninit: vnode => {
|
||||
vnode.state.timestamp = 0;
|
||||
vnode.state.ping = null;
|
||||
vnode.state.host = null;
|
||||
vnode.state.timeout = null;
|
||||
},
|
||||
view: vnode => {
|
||||
const refresh = () => {
|
||||
const device = vnode.attrs.device;
|
||||
let param =
|
||||
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
||||
if (!param)
|
||||
param = device["Device.ManagementServer.ConnectionRequestURL"];
|
||||
if (!param || !param.value) return;
|
||||
|
||||
const url = new URL(param.value[0]);
|
||||
vnode.state.host = url.hostname;
|
||||
vnode.state.timestamp = Date.now();
|
||||
store
|
||||
.ping(vnode.state.host)
|
||||
.then(res => {
|
||||
vnode.state.ping = res;
|
||||
})
|
||||
.catch(() => {
|
||||
// Do nothing
|
||||
});
|
||||
};
|
||||
const url = new URL(param.value[0]);
|
||||
vnode.state.host = url.hostname;
|
||||
vnode.state.timestamp = Date.now();
|
||||
store
|
||||
.ping(vnode.state.host)
|
||||
.then(res => {
|
||||
vnode.state.ping = res;
|
||||
})
|
||||
.catch(() => {
|
||||
// Do nothing
|
||||
});
|
||||
};
|
||||
|
||||
const t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now();
|
||||
clearTimeout(vnode.state.timeout);
|
||||
if (t <= 0) refresh();
|
||||
else vnode.state.timeout = setTimeout(refresh, t);
|
||||
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.avg != null)
|
||||
status = `${Math.trunc(vnode.state.ping.avg)} ms`;
|
||||
else status = "Unreachable";
|
||||
if (vnode.state.host) {
|
||||
let status = "";
|
||||
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 m("div", `Pinging ${vnode.state.host}: ${status}`);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
};
|
||||
}
|
||||
@@ -1,71 +1,71 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
import * as notifications from "../notifications";
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
return m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Initiate session and refresh basic parameters",
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const params = Object.values(vnode.attrs.parameters)
|
||||
.map(exp => {
|
||||
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
|
||||
return store.evaluateExpression(exp[1], device);
|
||||
})
|
||||
.filter(exp => !!exp);
|
||||
return m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Initiate session and refresh basic parameters",
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const params = Object.values(vnode.attrs.parameters)
|
||||
.map(exp => {
|
||||
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
|
||||
return store.evaluateExpression(exp[1], device);
|
||||
})
|
||||
.filter(exp => !!exp);
|
||||
|
||||
const task = {
|
||||
name: "getParameterValues",
|
||||
parameterNames: params,
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
};
|
||||
const task = {
|
||||
name: "getParameterValues",
|
||||
parameterNames: params,
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
};
|
||||
|
||||
taskQueue
|
||||
.commit(
|
||||
[task],
|
||||
(deviceId, err, connectionRequestStatus, tasks2) => {
|
||||
if (err) {
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
return;
|
||||
taskQueue
|
||||
.commit(
|
||||
[task],
|
||||
(deviceId, err, connectionRequestStatus, tasks2) => {
|
||||
if (err) {
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const t of tasks2)
|
||||
if (t.status === "stale") taskQueue.deleteTask(t);
|
||||
|
||||
if (connectionRequestStatus !== "OK") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
} else if (tasks2[0].status === "stale") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
);
|
||||
} else if (tasks2[0].status === "fault") {
|
||||
notifications.push("error", `${deviceId}: Refresh faulted`);
|
||||
} else {
|
||||
notifications.push("success", `${deviceId}: Summoned`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of tasks2)
|
||||
if (t.status === "stale") taskQueue.deleteTask(t);
|
||||
|
||||
if (connectionRequestStatus !== "OK") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
} else if (tasks2[0].status === "stale") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
);
|
||||
} else if (tasks2[0].status === "fault") {
|
||||
notifications.push("error", `${deviceId}: Refresh faulted`);
|
||||
} else {
|
||||
notifications.push("success", `${deviceId}: Summoned`);
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
"Summon"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
)
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
"Summon"
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+60
-54
@@ -1,37 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { m } from "../components";
|
||||
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;
|
||||
export default function component() {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
let writable = true;
|
||||
if (vnode.attrs.writable != null) writable = vnode.attrs.writable;
|
||||
|
||||
const tags = [];
|
||||
for (const p of Object.keys(device))
|
||||
if (p.startsWith("Tags.")) tags.push(p.slice(5));
|
||||
const tags = [];
|
||||
for (const p of Object.keys(device))
|
||||
if (p.startsWith("Tags.")) tags.push(p.slice(5));
|
||||
|
||||
tags.sort();
|
||||
tags.sort();
|
||||
|
||||
if (!writable) return m(".tags", tags.map(t => m("span.tag", t)));
|
||||
if (!writable) return m(".tags", tags.map(t => m("span.tag", t)));
|
||||
|
||||
return m(
|
||||
".tags",
|
||||
tags.map(tag =>
|
||||
return m(
|
||||
".tags",
|
||||
tags.map(tag =>
|
||||
m(
|
||||
"span.tag",
|
||||
tag,
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
store
|
||||
.updateTags(deviceId, { [tag]: false })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push(
|
||||
"success",
|
||||
`${deviceId}: Tags updated`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
e.target.disabled = false;
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${err.message}`
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
)
|
||||
)
|
||||
),
|
||||
m(
|
||||
"span.tag",
|
||||
tag,
|
||||
"span.tag.writable",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const tag = prompt(`Enter tag to assign to device:`);
|
||||
if (!tag) {
|
||||
e.target.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
store
|
||||
.updateTags(deviceId, { [tag]: false })
|
||||
.updateTags(deviceId, { [tag]: true })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
@@ -43,42 +81,10 @@ const component = {
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
"🞢"
|
||||
)
|
||||
)
|
||||
),
|
||||
m(
|
||||
"span.tag.writable",
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const tag = prompt(`Enter tag to assign to device:`);
|
||||
if (!tag) {
|
||||
e.target.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
store
|
||||
.updateTags(deviceId, { [tag]: true })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user