Update linting
This commit is contained in:
1 parent
4f2dfb4f48
commit
0c9c027a77
112 files changed
+3781
-4125
No files matched your search
@@ -17,14 +17,16 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { ClosureComponent } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import { parse } from "../../lib/common/expression/parser";
|
||||
import memoize from "../../lib/common/memoize";
|
||||
import { getIcon } from "../icons";
|
||||
import { evaluateExpression } from "../store";
|
||||
import { QueryResponse, evaluateExpression } from "../store";
|
||||
import debounce from "../../lib/common/debounce";
|
||||
import { Expression } from "../../lib/types";
|
||||
import { FlatDevice } from "../../lib/ui/db";
|
||||
|
||||
const memoizedParse = memoize(parse);
|
||||
|
||||
@@ -52,7 +54,13 @@ function orderKeysByDepth(device: Record<string, unknown>): string[][] {
|
||||
return res;
|
||||
}
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
interface Attrs {
|
||||
device: FlatDevice;
|
||||
limit: Expression;
|
||||
deviceQuery: QueryResponse;
|
||||
}
|
||||
|
||||
const component: ClosureComponent<Attrs> = () => {
|
||||
let queryString: string;
|
||||
const formQueryString = debounce((args: string[]) => {
|
||||
queryString = args[args.length - 1];
|
||||
@@ -61,9 +69,10 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
return {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
const limit = evaluateExpression(vnode.attrs["limit"], device) || 100;
|
||||
const limit =
|
||||
(evaluateExpression(vnode.attrs.limit, device) as number) || 100;
|
||||
|
||||
const search = m("input", {
|
||||
type: "text",
|
||||
@@ -108,8 +117,8 @@ const component: ClosureComponent = (): Component => {
|
||||
val.push(
|
||||
m(
|
||||
"parameter",
|
||||
Object.assign({ device: device, parameter: memoizedParse(k) })
|
||||
)
|
||||
Object.assign({ device: device, parameter: memoizedParse(k) }),
|
||||
),
|
||||
);
|
||||
} else if (p.object && p.writable) {
|
||||
if (instanceRegex.test(k)) {
|
||||
@@ -121,13 +130,13 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
device: device["DeviceID.ID"].value[0] as string,
|
||||
objectName: k,
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("delete-instance")
|
||||
)
|
||||
getIcon("delete-instance"),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
val.push(
|
||||
@@ -138,13 +147,13 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
device: device["DeviceID.ID"].value[0] as string,
|
||||
objectName: k,
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("add-instance")
|
||||
)
|
||||
getIcon("add-instance"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -157,38 +166,38 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "getParameterValues",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
device: device["DeviceID.ID"].value[0] as string,
|
||||
parameterNames: [k],
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("refresh")
|
||||
)
|
||||
getIcon("refresh"),
|
||||
),
|
||||
);
|
||||
|
||||
return m(
|
||||
"tr",
|
||||
attrs,
|
||||
m("td.left", m("long-text", { text: k })),
|
||||
m("td.right", val)
|
||||
m("td.right", val),
|
||||
);
|
||||
});
|
||||
|
||||
return m(
|
||||
"loading",
|
||||
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||
{ queries: [vnode.attrs.deviceQuery] },
|
||||
m(
|
||||
".all-parameters",
|
||||
m(
|
||||
"a.download-csv",
|
||||
{
|
||||
href: `api/devices/${encodeURIComponent(
|
||||
device["DeviceID.ID"].value[0]
|
||||
device["DeviceID.ID"].value[0],
|
||||
)}.csv`,
|
||||
download: "",
|
||||
style: "float: right;",
|
||||
},
|
||||
"Download"
|
||||
"Download",
|
||||
),
|
||||
search,
|
||||
m(
|
||||
@@ -196,10 +205,10 @@ const component: ClosureComponent = (): Component => {
|
||||
m("table", m("tbody", rows)),
|
||||
m(
|
||||
"m",
|
||||
`Displaying ${filteredKeys.length} out of ${count} parameters.`
|
||||
)
|
||||
)
|
||||
)
|
||||
`Displaying ${filteredKeys.length} out of ${count} parameters.`,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,18 +17,19 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Attributes, ClosureComponent, Component } from "mithril";
|
||||
import { Attributes, ClosureComponent } from "mithril";
|
||||
import { map } from "../../lib/common/expression/parser";
|
||||
import memoize from "../../lib/common/memoize";
|
||||
import { Expression } from "../../lib/types";
|
||||
import { m } from "../components";
|
||||
import { evaluateExpression, getTimestamp } from "../store";
|
||||
import { FlatDevice } from "../../lib/ui/db";
|
||||
|
||||
const evaluateAttributes = memoize(
|
||||
(
|
||||
attrs: Record<string, Expression>,
|
||||
obj: Record<string, unknown>,
|
||||
now: number // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
now: number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
): Attributes => {
|
||||
const res: Attributes = {};
|
||||
for (const [k, v] of Object.entries(attrs)) {
|
||||
@@ -47,19 +48,24 @@ const evaluateAttributes = memoize(
|
||||
res[k] = evaluateExpression(vv, obj);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
interface Attrs {
|
||||
device: FlatDevice;
|
||||
filter: Expression;
|
||||
components: unknown;
|
||||
}
|
||||
|
||||
const component: ClosureComponent<Attrs> = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const device = vnode.attrs.device;
|
||||
if ("filter" in vnode.attrs) {
|
||||
if (!evaluateExpression(vnode.attrs["filter"], device || {}))
|
||||
return null;
|
||||
if (!evaluateExpression(vnode.attrs.filter, device || {})) return null;
|
||||
}
|
||||
|
||||
const children = Object.values(vnode.attrs["components"]).map((c) => {
|
||||
const children = Object.values(vnode.attrs.components).map((c) => {
|
||||
if (Array.isArray(c)) c = evaluateExpression(c, device || {});
|
||||
if (typeof c !== "object") return `${c}`;
|
||||
const type = evaluateExpression(c["type"], device || {});
|
||||
@@ -79,7 +85,7 @@ const component: ClosureComponent = (): Component => {
|
||||
attrs = evaluateAttributes(
|
||||
el["attributes"],
|
||||
device || {},
|
||||
getTimestamp()
|
||||
getTimestamp(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
"Reboot"
|
||||
)
|
||||
"Reboot",
|
||||
),
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
@@ -58,8 +58,8 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
"Reset"
|
||||
)
|
||||
"Reset",
|
||||
),
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
@@ -74,8 +74,8 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
"Push file"
|
||||
)
|
||||
"Push file",
|
||||
),
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
@@ -98,8 +98,8 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
"Delete"
|
||||
)
|
||||
"Delete",
|
||||
),
|
||||
);
|
||||
|
||||
return m(".actions-bar", buttons);
|
||||
|
||||
@@ -76,8 +76,8 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("remove")
|
||||
)
|
||||
getIcon("remove"),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
@@ -86,19 +86,19 @@ const component: ClosureComponent = (): Component => {
|
||||
if (rows.length) {
|
||||
tbody = m(
|
||||
"tbody",
|
||||
rows.map((r) => m("tr", r))
|
||||
rows.map((r) => m("tr", r)),
|
||||
);
|
||||
} else {
|
||||
tbody = m(
|
||||
"tbody",
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No faults"))
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No faults")),
|
||||
);
|
||||
}
|
||||
|
||||
return m(
|
||||
"loading",
|
||||
{ queries: [faults] },
|
||||
m("table.table", thead, tbody)
|
||||
m("table.table", thead, tbody),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ const component: ClosureComponent = (): Component => {
|
||||
return m(
|
||||
"a",
|
||||
{ href: `#!/devices/${encodeURIComponent(deviceId)}` },
|
||||
children
|
||||
children,
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
|
||||
@@ -47,7 +47,7 @@ const component: ClosureComponent = (): Component => {
|
||||
cy: "0.5em",
|
||||
r: "0.4em",
|
||||
fill: slice["color"],
|
||||
})
|
||||
}),
|
||||
);
|
||||
return m("span.overview-dot", dot, `${slice["label"]}`);
|
||||
}
|
||||
|
||||
@@ -17,24 +17,35 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||
import { ClosureComponent, VnodeDOM } from "mithril";
|
||||
import { m } from "../components";
|
||||
import { evaluateExpression } from "../store";
|
||||
import { QueryResponse, evaluateExpression } from "../store";
|
||||
import { FlatDevice } from "../../lib/ui/db";
|
||||
import { Expression } from "../../lib/types";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
interface Attrs {
|
||||
device: FlatDevice;
|
||||
parameters: Record<
|
||||
string,
|
||||
{ type?: Expression; label: string; parameter: Expression }
|
||||
>;
|
||||
deviceQuery: QueryResponse;
|
||||
}
|
||||
|
||||
const component: ClosureComponent<Attrs> = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const device = vnode.attrs.device;
|
||||
|
||||
const rows = Object.values(vnode.attrs["parameters"]).map((parameter) => {
|
||||
const type = evaluateExpression(parameter["type"], device);
|
||||
const rows = Object.values(vnode.attrs.parameters).map((parameter) => {
|
||||
const type = evaluateExpression(parameter.type, device);
|
||||
const p = m.context(
|
||||
{
|
||||
device: device,
|
||||
parameter: parameter["parameter"],
|
||||
parameter: parameter.parameter,
|
||||
},
|
||||
(type as string) || "parameter",
|
||||
parameter
|
||||
parameter,
|
||||
);
|
||||
|
||||
return m(
|
||||
@@ -52,14 +63,14 @@ const component: ClosureComponent = (): Component => {
|
||||
},
|
||||
},
|
||||
m("th", parameter["label"]),
|
||||
m("td", p)
|
||||
m("td", p),
|
||||
);
|
||||
});
|
||||
|
||||
return m(
|
||||
"loading",
|
||||
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||
m("table.parameter-list", rows)
|
||||
{ queries: [vnode.attrs.deviceQuery] },
|
||||
m("table.parameter-list", rows),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,24 +17,35 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { ClosureComponent } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import { evaluateExpression } from "../store";
|
||||
import { QueryResponse, evaluateExpression } from "../store";
|
||||
import * as expressionParser from "../../lib/common/expression/parser";
|
||||
import { getIcon } from "../icons";
|
||||
import { FlatDevice } from "../../lib/ui/db";
|
||||
import { Expression } from "../../lib/types";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
interface Attrs {
|
||||
device: FlatDevice;
|
||||
parameter: Expression;
|
||||
label: Expression;
|
||||
childParameters: Record<string, { label: string; parameter: Expression }>;
|
||||
filter?: Expression;
|
||||
deviceQuery: QueryResponse;
|
||||
}
|
||||
|
||||
const component: ClosureComponent<Attrs> = () => {
|
||||
return {
|
||||
oninit: (vnode) => {
|
||||
const obj = vnode.attrs["parameter"];
|
||||
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"]);
|
||||
vnode.state["parameters"] = Object.values(vnode.attrs.childParameters);
|
||||
},
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const device = vnode.attrs.device;
|
||||
const object = evaluateExpression(vnode.state["object"], device);
|
||||
const parameters = vnode.state["parameters"];
|
||||
|
||||
@@ -56,7 +67,7 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
const rows = [];
|
||||
for (const i of instances) {
|
||||
let filter = "filter" in vnode.attrs ? vnode.attrs["filter"] : true;
|
||||
let filter = "filter" in vnode.attrs ? vnode.attrs.filter : true;
|
||||
|
||||
filter = expressionParser.map(filter, (e) => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
@@ -84,8 +95,8 @@ const component: ClosureComponent = (): Component => {
|
||||
device: device,
|
||||
parameter: param,
|
||||
label: null,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -100,14 +111,14 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "deleteObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
device: device["DeviceID.ID"].value[0] as string,
|
||||
objectName: i,
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("delete-instance")
|
||||
)
|
||||
)
|
||||
getIcon("delete-instance"),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
rows.push(m("tr", row));
|
||||
@@ -115,7 +126,7 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
if (!rows.length) {
|
||||
rows.push(
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
|
||||
m("tr.empty", m("td", { colspan: headers.length }, "No instances")),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,29 +144,29 @@ const component: ClosureComponent = (): Component => {
|
||||
onclick: () => {
|
||||
taskQueue.queueTask({
|
||||
name: "addObject",
|
||||
device: device["DeviceID.ID"].value[0],
|
||||
device: device["DeviceID.ID"].value[0] as string,
|
||||
objectName: object,
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("add-instance")
|
||||
)
|
||||
)
|
||||
)
|
||||
getIcon("add-instance"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let label;
|
||||
|
||||
const l = evaluateExpression(vnode.attrs["label"], device);
|
||||
const l = evaluateExpression(vnode.attrs.label, device);
|
||||
if (l != null) label = m("h2", l);
|
||||
|
||||
return [
|
||||
label,
|
||||
m(
|
||||
"loading",
|
||||
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||
m("table.table", thead, m("tbody", rows))
|
||||
{ queries: [vnode.attrs.deviceQuery] },
|
||||
m("table.table", thead, m("tbody", rows)),
|
||||
),
|
||||
];
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@ const component: ClosureComponent = (): Component => {
|
||||
const { value, timestamp, parameter } = evaluateParam(
|
||||
vnode.attrs["parameter"],
|
||||
device,
|
||||
store.getTimestamp() + store.getClockSkew()
|
||||
store.getTimestamp() + store.getClockSkew(),
|
||||
);
|
||||
|
||||
if (value == null) return null;
|
||||
@@ -103,7 +103,7 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("edit")
|
||||
getIcon("edit"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ const component: ClosureComponent = (): Component => {
|
||||
},
|
||||
},
|
||||
el,
|
||||
edit
|
||||
edit,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,19 +62,19 @@ const component: ClosureComponent = (): Component => {
|
||||
if (connectionRequestStatus !== "OK") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
`${deviceId}: ${connectionRequestStatus}`,
|
||||
);
|
||||
} else if (tasks2[0].status === "stale") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
`${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;
|
||||
@@ -86,7 +86,7 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
"Summon"
|
||||
"Summon",
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
+22
-15
@@ -17,20 +17,27 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { ClosureComponent } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as notifications from "../notifications";
|
||||
import * as store from "../store";
|
||||
import { getIcon } from "../icons";
|
||||
import { decodeTag } from "../../lib/util";
|
||||
import { Expression } from "../../lib/types";
|
||||
import { FlatDevice } from "../../lib/ui/db";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
interface Attrs {
|
||||
device: FlatDevice;
|
||||
writable?: Expression;
|
||||
}
|
||||
|
||||
const component: ClosureComponent<Attrs> = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
const device = vnode.attrs["device"];
|
||||
const device = vnode.attrs.device;
|
||||
let writable = true;
|
||||
if ("writable" in vnode.attrs)
|
||||
writable = !!store.evaluateExpression(vnode.attrs["writable"], device);
|
||||
writable = !!store.evaluateExpression(vnode.attrs.writable, device);
|
||||
|
||||
const tags = [];
|
||||
for (const p of Object.keys(device))
|
||||
@@ -41,7 +48,7 @@ const component: ClosureComponent = (): Component => {
|
||||
if (!writable) {
|
||||
return m(
|
||||
".tags",
|
||||
tags.map((t) => m("span.tag", t))
|
||||
tags.map((t) => m("span.tag", t)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,14 +63,14 @@ const component: ClosureComponent = (): Component => {
|
||||
{
|
||||
onclick: (e) => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const deviceId = device["DeviceID.ID"].value[0] as string;
|
||||
store
|
||||
.updateTags(deviceId, { [tag]: false })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push(
|
||||
"success",
|
||||
`${deviceId}: Tags updated`
|
||||
`${deviceId}: Tags updated`,
|
||||
);
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
@@ -71,14 +78,14 @@ const component: ClosureComponent = (): Component => {
|
||||
e.target.disabled = false;
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${err.message}`
|
||||
`${deviceId}: ${err.message}`,
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("remove")
|
||||
)
|
||||
)
|
||||
getIcon("remove"),
|
||||
),
|
||||
),
|
||||
),
|
||||
m(
|
||||
"span.tag.writable",
|
||||
@@ -88,7 +95,7 @@ const component: ClosureComponent = (): Component => {
|
||||
{
|
||||
onclick: (e) => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const deviceId = device["DeviceID.ID"].value[0] as string;
|
||||
const tag = prompt(`Enter tag to assign to device:`);
|
||||
if (!tag) {
|
||||
e.target.disabled = false;
|
||||
@@ -108,9 +115,9 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
},
|
||||
},
|
||||
getIcon("add")
|
||||
)
|
||||
)
|
||||
getIcon("add"),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user