Update ESLint and Prettier
This commit is contained in:
1 parent
5a9e03db2c
commit
ac980116bd
110 files changed
+2993
-2794
No files matched your search
@@ -32,31 +32,33 @@ function escapeRegExp(str): string {
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const search = m("input", {
|
||||
type: "text",
|
||||
placeholder: "Search parameters",
|
||||
oninput: e => {
|
||||
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);
|
||||
const keywords = vnode.state["searchString"]
|
||||
.split(" ")
|
||||
.filter((s) => s);
|
||||
if (keywords.length)
|
||||
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
|
||||
re = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
|
||||
}
|
||||
|
||||
const rows = Object.keys(device)
|
||||
.sort()
|
||||
.map(k => {
|
||||
.map((k) => {
|
||||
const p = device[k];
|
||||
const val = [];
|
||||
const attrs = {};
|
||||
@@ -83,9 +85,9 @@ const component: ClosureComponent = (): Component => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
objectName: k,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("delete-instance")
|
||||
)
|
||||
@@ -100,9 +102,9 @@ const component: ClosureComponent = (): Component => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: k
|
||||
objectName: k,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("add-instance")
|
||||
)
|
||||
@@ -119,9 +121,9 @@ const component: ClosureComponent = (): Component => {
|
||||
taskQueue.queueTask({
|
||||
name: "getParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
parameterNames: [k]
|
||||
parameterNames: [k],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("refresh")
|
||||
)
|
||||
@@ -144,14 +146,14 @@ const component: ClosureComponent = (): Component => {
|
||||
device["DeviceID.ID"].value[0]
|
||||
)}.csv`,
|
||||
download: "",
|
||||
style: "float: right;"
|
||||
style: "float: right;",
|
||||
},
|
||||
"Download"
|
||||
),
|
||||
search,
|
||||
m(".parameter-list", m("table", m("tbody", rows)))
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import * as store from "../store";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
if (vnode.attrs["filter"]) {
|
||||
if (
|
||||
!store.evaluateExpression(
|
||||
@@ -34,13 +34,13 @@ const component: ClosureComponent = (): Component => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = Object.values(vnode.attrs["components"]).map(c => {
|
||||
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;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import * as store from "../store";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const buttons = [];
|
||||
@@ -38,9 +38,9 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "reboot",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"Reboot"
|
||||
)
|
||||
@@ -54,9 +54,9 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "factoryReset",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"Reset"
|
||||
)
|
||||
@@ -70,9 +70,9 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.stageDownload({
|
||||
name: "download",
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"Push file"
|
||||
)
|
||||
@@ -93,17 +93,17 @@ const component: ClosureComponent = (): Component => {
|
||||
notifications.push("success", `${deviceId}: Device deleted`);
|
||||
m.route.set("/devices");
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"Delete"
|
||||
)
|
||||
);
|
||||
|
||||
return m(".actions-bar", buttons);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,13 +25,13 @@ import { getIcon } from "../icons";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
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`]
|
||||
["<", ["PARAM", "_id"], `${deviceId}:zzzz`],
|
||||
]);
|
||||
|
||||
const headers = [
|
||||
@@ -39,8 +39,8 @@ const component: ClosureComponent = (): Component => {
|
||||
"Code",
|
||||
"Message",
|
||||
"Retries",
|
||||
"Timestamp"
|
||||
].map(l => m("th", l));
|
||||
"Timestamp",
|
||||
].map((l) => m("th", l));
|
||||
const thead = m("thead", m("tr", headers));
|
||||
|
||||
const rows = [];
|
||||
@@ -57,7 +57,7 @@ const component: ClosureComponent = (): Component => {
|
||||
"button",
|
||||
{
|
||||
title: "Delete fault",
|
||||
onclick: e => {
|
||||
onclick: (e) => {
|
||||
e.redraw = false;
|
||||
store
|
||||
.deleteResource("faults", f["_id"])
|
||||
@@ -66,21 +66,24 @@ const component: ClosureComponent = (): Component => {
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
m.redraw();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("remove")
|
||||
)
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
let tbody;
|
||||
if (rows.length) {
|
||||
tbody = m("tbody", rows.map(r => m("tr", r)));
|
||||
tbody = m(
|
||||
"tbody",
|
||||
rows.map((r) => m("tr", r))
|
||||
);
|
||||
} else {
|
||||
tbody = m(
|
||||
"tbody",
|
||||
@@ -89,7 +92,7 @@ const component: ClosureComponent = (): Component => {
|
||||
}
|
||||
|
||||
return m("table.table", thead, tbody);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -22,12 +22,12 @@ import { m } from "../components";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
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 => {
|
||||
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);
|
||||
@@ -41,7 +41,7 @@ const component: ClosureComponent = (): Component => {
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ const CHARTS = config.ui.overview.charts;
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const chart = CHARTS[vnode.attrs["chart"]];
|
||||
const chart = CHARTS[vnode.attrs["chart"]] as Record<string, unknown>;
|
||||
if (!chart) return null;
|
||||
for (const slice of Object.values(chart.slices)) {
|
||||
const filter = slice["filter"];
|
||||
@@ -39,20 +39,20 @@ const component: ClosureComponent = (): Component => {
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
||||
},
|
||||
m("circle", {
|
||||
cx: "0.5em",
|
||||
cy: "0.5em",
|
||||
r: "0.4em",
|
||||
fill: slice["color"]
|
||||
fill: slice["color"],
|
||||
})
|
||||
);
|
||||
return m("span.overview-dot", dot, `${slice["label"]}`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ import { m } from "../components";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const rows = Object.values(vnode.attrs["parameters"]).map(parameter => {
|
||||
const rows = Object.values(vnode.attrs["parameters"]).map((parameter) => {
|
||||
const p = m.context(
|
||||
{
|
||||
device: device,
|
||||
parameter: parameter["parameter"]
|
||||
parameter: parameter["parameter"],
|
||||
},
|
||||
parameter["type"] || "parameter",
|
||||
parameter
|
||||
@@ -38,11 +38,11 @@ const component: ClosureComponent = (): Component => {
|
||||
return m(
|
||||
"tr",
|
||||
{
|
||||
onupdate: vn => {
|
||||
onupdate: (vn) => {
|
||||
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||
? ""
|
||||
: "none";
|
||||
}
|
||||
},
|
||||
},
|
||||
m("th", parameter["label"]),
|
||||
m("td", p)
|
||||
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
|
||||
return m("table.parameter-list", rows);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -26,21 +26,21 @@ import { getIcon } from "../icons";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
oninit: vnode => {
|
||||
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 => {
|
||||
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 (typeof object !== "string" || !device[object]) return null;
|
||||
|
||||
const instances = new Set();
|
||||
const instances: Set<string> = new Set();
|
||||
const prefix = `${object}.`;
|
||||
for (const p in device) {
|
||||
if (p.startsWith(prefix)) {
|
||||
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -59,7 +59,7 @@ const component: ClosureComponent = (): Component => {
|
||||
let filter =
|
||||
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
||||
|
||||
filter = expressionParser.map(filter, e => {
|
||||
filter = expressionParser.map(filter, (e) => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
return ["PARAM", ["||", i, ".", e[1]]];
|
||||
return e;
|
||||
@@ -67,8 +67,8 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
if (!store.evaluateExpression(filter, device)) continue;
|
||||
|
||||
const row = parameters.map(p => {
|
||||
const param = expressionParser.map(p.parameter, e => {
|
||||
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;
|
||||
@@ -78,13 +78,13 @@ const component: ClosureComponent = (): Component => {
|
||||
m.context(
|
||||
{
|
||||
device: device,
|
||||
parameter: param
|
||||
parameter: param,
|
||||
},
|
||||
p.type || "parameter",
|
||||
Object.assign({}, p, {
|
||||
device: device,
|
||||
parameter: param,
|
||||
label: null
|
||||
label: null,
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -102,9 +102,9 @@ const component: ClosureComponent = (): Component => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: i
|
||||
objectName: i,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("delete-instance")
|
||||
)
|
||||
@@ -135,9 +135,9 @@ const component: ClosureComponent = (): Component => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
objectName: object
|
||||
objectName: object,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("add-instance")
|
||||
)
|
||||
@@ -150,7 +150,7 @@ const component: ClosureComponent = (): Component => {
|
||||
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
||||
|
||||
return [label, m("table.table", thead, m("tbody", rows))];
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import { getIcon } from "../icons";
|
||||
const evaluateParam = memoize((exp, obj, now: number) => {
|
||||
let timestamp = now;
|
||||
const params = new Set();
|
||||
const value = expression.evaluate(exp, obj, now, e => {
|
||||
const value = expression.evaluate(exp, obj, now, (e) => {
|
||||
if (Array.isArray(e)) {
|
||||
if (e[0] === "PARAM") {
|
||||
params.add(e[1]);
|
||||
@@ -52,7 +52,7 @@ const evaluateParam = memoize((exp, obj, now: number) => {
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const { value, timestamp, parameter } = evaluateParam(
|
||||
@@ -77,11 +77,11 @@ const component: ClosureComponent = (): Component => {
|
||||
[
|
||||
parameter,
|
||||
device[parameter].value[0],
|
||||
device[parameter].value[1]
|
||||
]
|
||||
]
|
||||
device[parameter].value[1],
|
||||
],
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("edit")
|
||||
);
|
||||
@@ -93,7 +93,7 @@ const component: ClosureComponent = (): Component => {
|
||||
"span",
|
||||
{
|
||||
class: "parameter-value",
|
||||
onmouseover: e => {
|
||||
onmouseover: (e) => {
|
||||
e.redraw = false;
|
||||
// Don't update any child element
|
||||
if (e.target === (el as VnodeDOM).dom) {
|
||||
@@ -101,12 +101,12 @@ const component: ClosureComponent = (): Component => {
|
||||
const localeString = new Date(timestamp).toLocaleString();
|
||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
el,
|
||||
edit
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const component: ClosureComponent = (vn): Component => {
|
||||
onremove: () => {
|
||||
clearInterval(interval);
|
||||
},
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
let param =
|
||||
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
||||
@@ -73,7 +73,7 @@ const component: ClosureComponent = (vn): Component => {
|
||||
}
|
||||
|
||||
return m("div", `Pinging ${host}:`);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,26 +25,26 @@ import * as notifications from "../notifications";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
return m(
|
||||
"button.primary",
|
||||
{
|
||||
title: "Initiate session and refresh basic parameters",
|
||||
onclick: e => {
|
||||
onclick: (e) => {
|
||||
e.target.disabled = true;
|
||||
const params = Object.values(vnode.attrs["parameters"])
|
||||
.map(exp => {
|
||||
.map((exp) => {
|
||||
if (!Array.isArray(exp) || exp[0] !== "PARAM") return null;
|
||||
return store.evaluateExpression(exp[1], device);
|
||||
return store.evaluateExpression(exp[1], device) as string;
|
||||
})
|
||||
.filter(exp => !!exp);
|
||||
.filter((exp) => !!exp);
|
||||
|
||||
const task = {
|
||||
name: "getParameterValues",
|
||||
parameterNames: params,
|
||||
device: device["DeviceID.ID"].value[0]
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
};
|
||||
|
||||
taskQueue
|
||||
@@ -80,11 +80,11 @@ const component: ClosureComponent = (): Component => {
|
||||
e.target.disabled = false;
|
||||
store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"Summon"
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+15
-10
@@ -25,7 +25,7 @@ import { getIcon } from "../icons";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
let writable = true;
|
||||
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
|
||||
@@ -36,18 +36,23 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
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 =>
|
||||
tags.map((tag) =>
|
||||
m(
|
||||
"span.tag",
|
||||
tag,
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
onclick: (e) => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
store
|
||||
@@ -60,14 +65,14 @@ const component: ClosureComponent = (): Component => {
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
e.target.disabled = false;
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${err.message}`
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("remove")
|
||||
)
|
||||
@@ -79,7 +84,7 @@ const component: ClosureComponent = (): Component => {
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
onclick: (e) => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const tag = prompt(`Enter tag to assign to device:`);
|
||||
@@ -95,17 +100,17 @@ const component: ClosureComponent = (): Component => {
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
getIcon("add")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user