Update ESLint and Prettier

This commit is contained in:
Zaid Abdulla committed 2020-07-27 20:30:13 -07:00
1 parent 5a9e03db2c
commit ac980116bd
110 files changed
+2993 -2794

No files matched your search

+2 -2
View File
@@ -22,7 +22,7 @@ import { ClosureComponent, Component } from "mithril";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const active = { [vnode.attrs["page"]]: "active" };
const tabs = [];
@@ -97,7 +97,7 @@ const component: ClosureComponent = (): Component => {
}
return m("nav#side-menu", m("ul", tabs));
}
},
};
};
+8 -8
View File
@@ -59,7 +59,7 @@ const adminPages = [
"files",
"config",
"users",
"permissions"
"permissions",
];
let state;
@@ -71,7 +71,7 @@ let fulfillTimeout = null;
function fulfill(): void {
clearTimeout(fulfillTimeout);
fulfillTimeout = setTimeout(() => {
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => {
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then((updated) => {
if (updated) m.redraw();
});
}, 100);
@@ -89,7 +89,7 @@ function pagify(pageName, page): RouteResolver {
attrs["page"] = pageName;
return m(layout, attrs, p);
},
onmatch: null
onmatch: null,
};
component.onmatch = (args, requestedPath) => {
@@ -100,16 +100,16 @@ function pagify(pageName, page): RouteResolver {
return null;
}
return new Promise(resolve => {
return new Promise((resolve) => {
page
.init(args)
.then(st => {
.then((st) => {
if (!st) return void m.route.set("/");
state = st;
resolve();
fulfill();
})
.catch(err => {
.catch((err) => {
if (!window.username && err.message.indexOf("authorized") >= 0) {
notifications.push("error", err.message);
m.route.set("/login", { continue: requestedPath });
@@ -133,7 +133,7 @@ function redirectAdminPage(): RouteResolver {
}
}
return null;
}
},
};
return component;
}
@@ -155,5 +155,5 @@ m.route(document.body, "/overview", {
"/admin/files": pagify("files", filesPage),
"/admin/config": pagify("config", configPage),
"/admin/users": pagify("users", usersPage),
"/admin/permissions": pagify("permissions", permissionsPage)
"/admin/permissions": pagify("permissions", permissionsPage),
});
+11 -9
View File
@@ -17,11 +17,13 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
type AutocompleteCallback = (
value: string,
callback: (suggestions: { value: string; tip?: string }[]) => void
) => void;
export default class Autocomplete {
private callback: (
value: string,
callback: (suggestions: { value: string; tip?: string }[]) => void
) => void;
private callback: AutocompleteCallback;
private element: HTMLInputElement;
private hideTimeout: NodeJS.Timeout;
private visible: boolean;
@@ -29,7 +31,7 @@ export default class Autocomplete {
private selection: number;
private container: HTMLElement;
public constructor(className, callback) {
public constructor(className: string, callback: AutocompleteCallback) {
this.callback = callback;
this.element = null;
this.hideTimeout = null;
@@ -44,7 +46,7 @@ export default class Autocomplete {
this.container.className = className;
}
public attach(el): void {
public attach(el: HTMLInputElement): void {
el.setAttribute("autocomplete", "off");
el.addEventListener("focus", () => {
@@ -62,7 +64,7 @@ export default class Autocomplete {
this.hide();
});
el.addEventListener("keydown", e => {
el.addEventListener("keydown", (e) => {
if (this.element !== el) return;
if (e.key === "Escape") {
if (this.visible) this.hide();
@@ -108,7 +110,7 @@ export default class Autocomplete {
private update(): void {
const el = this.element;
this.callback(el.value, suggestions => {
this.callback(el.value, (suggestions) => {
if (this.element !== el) return;
this.default = null;
@@ -154,7 +156,7 @@ export default class Autocomplete {
const t = document.createTextNode(suggestion.value);
e.appendChild(t);
e.addEventListener("mousedown", ev => {
e.addEventListener("mousedown", (ev) => {
ev.preventDefault();
el.value = suggestion.value;
if (this.element === el) this.update();
+14 -14
View File
@@ -24,7 +24,7 @@ import { m } from "./components";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const onPasswordChange = vnode.attrs["onPasswordChange"];
const enforceAuth = !vnode.attrs["noAuth"];
const username = vnode.attrs["username"];
@@ -40,19 +40,19 @@ const component: ClosureComponent = (): Component => {
type: "text",
value: vnode.state["username"],
disabled: !!username,
oninput: e => {
oninput: (e) => {
vnode.state["username"] = e.target.value;
},
oncreate: _vnode => {
oncreate: (_vnode) => {
(_vnode.dom as HTMLSelectElement).focus();
}
},
})
)
),
];
let fields = {
newPassword: "New password",
confirmPassword: "Confirm password"
confirmPassword: "Confirm password",
};
if (enforceAuth)
fields = Object.assign({ authPassword: "Your password" }, fields);
@@ -66,9 +66,9 @@ const component: ClosureComponent = (): Component => {
name: f,
type: "password",
value: vnode.state[f],
oninput: e => {
oninput: (e) => {
vnode.state[f] = e.target.value;
}
},
})
)
);
@@ -77,7 +77,7 @@ const component: ClosureComponent = (): Component => {
const submit = m(
"button.primary",
{
type: "submit"
type: "submit",
},
"Change password"
) as VnodeDOM;
@@ -89,7 +89,7 @@ const component: ClosureComponent = (): Component => {
m(
"form",
{
onsubmit: e => {
onsubmit: (e) => {
e.redraw = false;
e.preventDefault();
if (
@@ -120,19 +120,19 @@ const component: ClosureComponent = (): Component => {
if (onPasswordChange) onPasswordChange();
(submit.dom as HTMLFormElement).disabled = false;
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
(submit.dom as HTMLFormElement).disabled = false;
});
}
}
},
},
form
)
),
];
return m("div.put-form", children);
}
},
};
};
+7 -7
View File
@@ -23,11 +23,11 @@ import { codeMirror } from "./dynamic-loader";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
return m("textarea", {
name: vnode.attrs["id"],
value: vnode.attrs["value"],
oncreate: _vnode => {
oncreate: (_vnode) => {
const editor = codeMirror.fromTextArea(_vnode.dom, {
mode: vnode.attrs["mode"],
lineNumbers: true,
@@ -40,21 +40,21 @@ const component: ClosureComponent = (): Component => {
"Cmd-Enter": () => {
if (vnode.attrs["onSubmit"])
vnode.attrs["onSubmit"](_vnode.dom);
}
}
},
},
});
if (vnode.attrs["onChange"]) {
editor.on("change", e => {
editor.on("change", (e) => {
vnode.attrs["onChange"](e.getValue());
});
}
if (vnode.attrs["focus"]) editor.focus();
if (vnode.attrs["onReady"]) vnode.attrs["onReady"](editor);
}
},
});
}
},
};
};
+6 -6
View File
@@ -24,7 +24,7 @@ import m, {
ComponentTypes,
Lifecycle,
ClosureComponent,
Vnode
Vnode,
} from "mithril";
import parameter from "./components/parameter";
import parameterList from "./components/parameter-list";
@@ -53,7 +53,7 @@ const comps = {
tags,
ping,
"device-link": deviceLink,
"long-text": longTextComponent
"long-text": longTextComponent,
};
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
@@ -96,7 +96,7 @@ const M = new Proxy(m, {
get: (target, prop) => {
if (prop === "context") return contextFn;
else return Reflect.get(target, prop);
}
},
}) as MC;
function contextFn(context, ...argumentsList): Vnode {
@@ -125,17 +125,17 @@ export function contextifyComponent(component: ComponentTypes): ComponentTypes {
if (typeof component !== "function") {
c = Object.assign({}, component);
const view = component.view;
c.view = function(vnode) {
c.view = function (vnode) {
const context = vnodeContext.get(vnode) || {};
const res = Reflect.apply(view, this, [vnode]);
applyContext(res, context);
return res;
};
} else if (!component.prototype || !component.prototype.view) {
c = initialNode => {
c = (initialNode) => {
const state = (component as ClosureComponent)(initialNode);
const view = state.view;
state.view = function(vnode) {
state.view = function (vnode) {
const context = vnodeContext.get(vnode) || {};
const res = Reflect.apply(view, this, [vnode]);
applyContext(res, context);
+16 -14
View File
@@ -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)))
);
}
},
};
};
+3 -3
View File
@@ -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;
}
},
};
};
+10 -10
View File
@@ -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);
}
},
};
};
+13 -10
View File
@@ -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);
}
},
};
};
+3 -3
View File
@@ -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;
}
}
},
};
};
+5 -5
View File
@@ -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;
}
},
};
};
+6 -6
View File
@@ -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);
}
},
};
};
+15 -15
View File
@@ -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))];
}
},
};
};
+9 -9
View File
@@ -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
);
}
},
};
};
+2 -2
View File
@@ -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}:`);
}
},
};
};
+8 -8
View File
@@ -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
View File
@@ -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")
)
)
);
}
},
};
};
+7 -4
View File
@@ -27,7 +27,7 @@ interface Diff {
remove: string[];
}
export function flattenConfig(config): any {
export function flattenConfig(config: Record<string, unknown>): any {
const flatten = {};
const recuresive = (obj: any, root: string): void => {
for (const [k, v] of Object.entries(obj)) {
@@ -56,7 +56,7 @@ export function structureConfig(config: Config[]): any {
ref[keys[0]] = c.value;
}
const toArray = function(object): any {
const toArray = function (object): any {
const MAX_BITS = 30;
const MAX_ARRAY_SIZE = MAX_BITS * 10;
@@ -96,10 +96,13 @@ export function structureConfig(config: Config[]): any {
return toArray(_config);
}
export function diffConfig(current, target): Diff {
export function diffConfig(
current: Record<string, unknown>,
target: Record<string, unknown>
): Diff {
const diff = {
add: [],
remove: []
remove: [],
};
for (const [k, v] of Object.entries(target))
+30 -30
View File
@@ -30,7 +30,7 @@ import { getIcon } from "./icons";
const attributes = [
{ id: "_id", label: "Key" },
{ id: "value", label: "Value", type: "textarea" }
{ id: "value", label: "Value", type: "textarea" },
];
interface ValidationErrors {
@@ -52,13 +52,13 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
object.value = stringify(parse(object.value || ""));
} catch (err) {
return void resolve({
value: "Config value must be valid expression"
value: "Config value must be valid expression",
});
}
store
.resourceExists("config", id)
.then(exists => {
.then((exists) => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Config already exists" });
@@ -90,7 +90,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
store.fulfill(0, Date.now());
reject(err);
});
@@ -102,14 +102,14 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
const formData = {
resource: "config",
attributes: attributes
attributes: attributes,
};
function escapeRegExp(str): string {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}
export function init(): Promise<{}> {
export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("config", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -131,9 +131,9 @@ function renderTable(confsResponse, searchString): Children {
let regex;
if (searchString) {
const keywords = searchString.split(" ").filter(s => s);
const keywords = searchString.split(" ").filter((s) => s);
if (keywords.length)
regex = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
regex = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
}
const rows = [];
@@ -154,9 +154,9 @@ function renderTable(confsResponse, searchString): Children {
{
base: conf,
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, false)
.then(errors => {
.then((errors) => {
const ErrorList = errors ? Object.values(errors) : [];
if (ErrorList.length) {
for (const err of ErrorList)
@@ -166,19 +166,19 @@ function renderTable(confsResponse, searchString): Children {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
getIcon("edit")
);
@@ -190,10 +190,10 @@ function renderTable(confsResponse, searchString): Children {
onclick: () => {
if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return;
putActionHandler("delete", conf).catch(err => {
putActionHandler("delete", conf).catch((err) => {
throw err;
});
}
},
},
getIcon("remove")
);
@@ -219,18 +219,18 @@ function renderTable(confsResponse, searchString): Children {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Config - GenieACS";
const search = m("input", {
type: "text",
placeholder: "Search config",
oninput: e => {
oninput: (e) => {
vnode.state["searchString"] = e.target.value;
e.redraw = false;
clearTimeout(vnode.state["timeout"]);
vnode.state["timeout"] = setTimeout(m.redraw, 250);
}
},
});
const confs = store.fetch("config", true);
@@ -250,9 +250,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, true)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -264,19 +264,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New config"
);
@@ -286,7 +286,7 @@ export const component: ClosureComponent = (): Component => {
{ name: "charts", prefix: "ui.overview.charts.", data: [] },
{ name: "filters", prefix: "ui.filters.", data: [] },
{ name: "index page", prefix: "ui.index.", data: [] },
{ name: "device page", prefix: "ui.device.", data: [] }
{ name: "device page", prefix: "ui.device.", data: [] },
];
if (confs.fulfilled) {
@@ -312,7 +312,7 @@ export const component: ClosureComponent = (): Component => {
uiConfigComponent,
Object.assign(
{
onUpdate: errs => {
onUpdate: (errs: string[]) => {
const errors = errs ? Object.values(errs) : [];
if (errors.length) {
for (const err of errors)
@@ -329,18 +329,18 @@ export const component: ClosureComponent = (): Component => {
}
store.fulfill(0, Date.now());
},
onError: err => {
onError: (err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
overlay.close(cb);
}
},
},
attrs
)
);
};
overlay.open(cb);
}
},
},
`Edit ${sub.name}`
)
@@ -359,8 +359,8 @@ export const component: ClosureComponent = (): Component => {
renderTable(confs, vnode.state["searchString"])
),
m(".actions-bar", [newConfig].concat(subs))
)
),
];
}
},
};
};
+6 -4
View File
@@ -22,7 +22,9 @@ import { m } from "./components";
import config from "./config";
import * as store from "./store";
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -31,13 +33,13 @@ export function init(args): Promise<{}> {
return Promise.resolve({
deviceId: args.id,
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id]
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id],
});
}
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
@@ -49,6 +51,6 @@ export const component: ClosureComponent = (): Component => {
cmps.push(m.context({ device: dev[0] }, c["type"], c));
return [m("h1", vnode.attrs["deviceId"]), cmps];
}
},
};
};
+24 -22
View File
@@ -35,7 +35,7 @@ const PAGE_SIZE = config.ui.pageSize || 10;
const memoizedParse = memoize(parse);
const memoizedJsonParse = memoize(JSON.parse);
const memoizedGetSortable = memoize(p => {
const memoizedGetSortable = memoize((p) => {
const expressionParams = extractParams(p);
if (expressionParams.length === 1) {
const param = evaluate(expressionParams[0]);
@@ -49,19 +49,21 @@ const getDownloadUrl = memoize((filter, indexParameters) => {
for (const p of indexParameters) columns[p.label] = stringify(p.parameter);
return `api/devices.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(columns)
columns: JSON.stringify(columns),
})}`;
});
const unpackSmartQuery = memoize(query => {
return expressionParser.map(query, e => {
const unpackSmartQuery = memoize((query) => {
return expressionParser.map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("devices", e[2], e[3]);
return e;
});
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
return new Promise((resolve, reject) => {
if (!window.authorizer.hasAccess("devices", 2))
return void reject(new Error("You are not authorized to view this page"));
@@ -72,14 +74,14 @@ export function init(args): Promise<{}> {
if (!indexParameters.length) {
indexParameters.push({
label: "ID",
parameter: ["PARAM", "DeviceID.ID"]
parameter: ["PARAM", "DeviceID.ID"],
});
}
resolve({ filter, indexParameters, sort });
});
}
function renderActions(selected): Children {
function renderActions(selected: Set<string>): Children {
const buttons = [];
buttons.push(
@@ -92,10 +94,10 @@ function renderActions(selected): Children {
for (const d of selected) {
taskQueue.queueTask({
name: "reboot",
device: d
device: d,
});
}
}
},
},
"Reboot"
)
@@ -111,10 +113,10 @@ function renderActions(selected): Children {
for (const d of selected) {
taskQueue.queueTask({
name: "factoryReset",
device: d
device: d,
});
}
}
},
},
"Reset"
)
@@ -139,13 +141,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Deleted`);
if (--counter === 0) store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now());
});
}
if (--counter === 0) store.fulfill(0, Date.now());
}
},
},
"Delete"
)
@@ -171,13 +173,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Tags updated`);
if (--counter === 0) store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now());
});
}
if (--counter === 0) store.fulfill(0, Date.now());
}
},
},
"Tag"
)
@@ -205,13 +207,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Tags updated`);
if (--counter === 0) store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now());
});
}
if (--counter === 0) store.fulfill(0, Date.now());
}
},
},
"Untag"
)
@@ -222,7 +224,7 @@ function renderActions(selected): Children {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Devices - GenieACS";
const attributes = vnode.attrs["indexParameters"];
@@ -273,7 +275,7 @@ export const component: ClosureComponent = (): Component => {
const devs = store.fetch("devices", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("devices", filter);
@@ -302,7 +304,7 @@ export const component: ClosureComponent = (): Component => {
{
href: `#!/devices/${encodeURIComponent(
device["DeviceID.ID"].value[0]
)}`
)}`,
},
"Show"
);
@@ -319,8 +321,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing devices"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+36 -36
View File
@@ -22,7 +22,7 @@ import m, {
Child,
ClosureComponent,
Component,
VnodeDOM
VnodeDOM,
} from "mithril";
import * as taskQueue from "./task-queue";
import * as store from "./store";
@@ -50,18 +50,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
"select",
{
value: task.parameterValues[0][1].toString(),
onchange: e => {
onchange: (e) => {
e.redraw = false;
task.parameterValues[0][1] = input.dom.value;
},
onkeydown: keydown,
oncreate: vnode => {
oncreate: (vnode) => {
(vnode.dom as HTMLSelectElement).focus();
}
},
},
[
m("option", { value: "true" }, "true"),
m("option", { value: "false" }, "false")
m("option", { value: "false" }, "false"),
]
);
} else {
@@ -72,18 +72,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
? "number"
: "text",
value: task.parameterValues[0][1],
oninput: e => {
oninput: (e) => {
e.redraw = false;
task.parameterValues[0][1] = input.dom.value;
},
onkeydown: keydown,
oncreate: vnode => {
oncreate: (vnode) => {
(vnode.dom as HTMLInputElement).focus();
(vnode.dom as HTMLInputElement).select();
// Need to prevent scrolling on focus because
// we're animating height and using overflow: hidden
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
}
},
});
}
@@ -104,8 +104,8 @@ function renderStagingDownload(task): Children {
"2 Web Content",
"3 Vendor Configuration File",
"4 Tone File",
"5 Ringer File"
].map(t =>
"5 Ringer File",
].map((t) =>
m(
"option",
{ disabled: !t, value: t, selected: (task.fileType || "") === t },
@@ -117,14 +117,14 @@ function renderStagingDownload(task): Children {
.concat(
files.value
.filter(
f =>
(f) =>
(!f["metadata.oui"] || f["metadata.oui"] === oui) &&
(!f["metadata.productClass"] ||
f["metadata.productClass"] === productClass)
)
.map(f => f._id)
.map((f) => f._id)
)
.map(f =>
.map((f) =>
m(
"option",
{ disabled: !f, value: f, selected: (task.fileName || "") === f },
@@ -137,7 +137,7 @@ function renderStagingDownload(task): Children {
m(
"select",
{
onchange: e => {
onchange: (e) => {
const f = e.target.value;
task.fileName = f;
task.fileType = "";
@@ -145,7 +145,7 @@ function renderStagingDownload(task): Children {
if (file._id === f) task.fileType = file["metadata.fileType"];
},
disabled: files.fulfilling,
style: "width: 350px"
style: "width: 350px",
},
filesList
),
@@ -153,12 +153,12 @@ function renderStagingDownload(task): Children {
m(
"select",
{
onchange: e => {
onchange: (e) => {
task.fileType = e.target.value;
}
},
},
typesList
)
),
];
}
@@ -216,7 +216,7 @@ function renderQueue(queue): Child[] {
title: "Retry this task",
onclick: () => {
taskQueue.queueTask(t);
}
},
},
getIcon("retry")
)
@@ -230,7 +230,7 @@ function renderQueue(queue): Child[] {
title: "Remove this task",
onclick: () => {
taskQueue.deleteTask(t);
}
},
},
getIcon("remove")
)
@@ -324,18 +324,18 @@ function renderNotifications(notifs): Child[] {
{
class: n.type,
style: "position: absolute;opacity: 0",
oncreate: vnode => {
oncreate: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "1";
},
onbeforeremove: vnode => {
onbeforeremove: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "0";
return new Promise(resolve => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 500);
});
},
key: n.timestamp
key: n.timestamp,
},
m("div", buttons, n.message)
)
@@ -346,7 +346,7 @@ function renderNotifications(notifs): Child[] {
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const queue = taskQueue.getQueue();
const staging = taskQueue.getStaging();
const notifs = notifications.getNotifications();
@@ -395,7 +395,7 @@ const component: ClosureComponent = (): Component => {
disabled: !statusCount.queued,
onclick: () => {
const tasks = Array.from(taskQueue.getQueue()).filter(
t => t["status"] === "queued"
(t) => t["status"] === "queued"
);
taskQueue
.commit(
@@ -442,7 +442,7 @@ const component: ClosureComponent = (): Component => {
.then(() => {
store.fulfill(0, Date.now());
});
}
},
},
"Commit"
),
@@ -451,7 +451,7 @@ const component: ClosureComponent = (): Component => {
{
title: "Clear tasks",
onclick: taskQueue.clear,
disabled: !queueElements.length
disabled: !queueElements.length,
},
"Clear"
)
@@ -487,31 +487,31 @@ const component: ClosureComponent = (): Component => {
{
key: "drawer",
style: "opacity: 0;height: 0;",
oncreate: vnode2 => {
oncreate: (vnode2) => {
vnode.state["mouseIn"] = false;
(vnode2.dom as HTMLDivElement).style.opacity = "1";
resizeDrawer();
},
onmouseover: e => {
onmouseover: (e) => {
vnode.state["mouseIn"] = true;
resizeDrawer();
e.redraw = false;
},
onmouseleave: e => {
onmouseleave: (e) => {
vnode.state["mouseIn"] = false;
resizeDrawer();
e.redraw = false;
},
onupdate: resizeDrawer,
onbeforeremove: vnode2 => {
onbeforeremove: (vnode2) => {
(vnode2.dom as HTMLDivElement).onmouseover = null;
(vnode2.dom as HTMLDivElement).onmouseleave = null;
(vnode2.dom as HTMLDivElement).style.opacity = "0";
(vnode2.dom as HTMLDivElement).style.height = "0";
return new Promise(resolve => {
return new Promise((resolve) => {
setTimeout(resolve, 500);
});
}
},
},
statusElement,
stagingElements.length ? stagingElements : m(".queue", queueElements)
@@ -527,12 +527,12 @@ const component: ClosureComponent = (): Component => {
key: "notifications",
style: "position: relative;",
onupdate: repositionNotifications,
oncreate: repositionNotifications
oncreate: repositionNotifications,
},
notificationElements
)
);
}
},
};
};
+6 -6
View File
@@ -32,7 +32,7 @@ function onError(): void {
{
Reload: () => {
window.location.reload();
}
},
}
);
}
@@ -47,14 +47,14 @@ export function loadCodeMirror(): Promise<void> {
import(
/* webpackChunkName: "codemirror" */ "codemirror/mode/javascript/javascript"
),
import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml")
import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml"),
];
Promise.all(promises)
.then(modules => {
.then((modules) => {
codeMirror = modules[0];
resolve();
})
.catch(err => {
.catch((err) => {
onError();
reject(err);
});
@@ -66,11 +66,11 @@ export function loadYaml(): Promise<void> {
return new Promise((resolve, reject) => {
import(/* webpackChunkName: "yaml" */ "yaml")
.then(module => {
.then((module) => {
yaml = module;
resolve();
})
.catch(err => {
.catch((err) => {
onError();
reject(err);
});
+2 -2
View File
@@ -22,9 +22,9 @@ import { m } from "./components";
export const component: ClosureComponent = (): Component => {
return {
view: function(vnode) {
view: function (vnode) {
document.title = "Error! - GenieACS";
return m("p.error", vnode.attrs["error"]);
}
},
};
};
+18 -16
View File
@@ -41,10 +41,10 @@ const attributes = [
{ id: "message", label: "Message" },
{ id: "detail", label: "Detail" },
{ id: "retries", label: "Retries" },
{ id: "timestamp", label: "Timestamp" }
{ id: "timestamp", label: "Timestamp" },
];
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) {
cols[attr.label] =
@@ -53,19 +53,21 @@ const getDownloadUrl = memoize(filter => {
return `api/faults.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("faults", e[2], e[3]);
return e;
});
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("faults", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -85,7 +87,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Faults - GenieACS";
function showMore(): void {
@@ -130,7 +132,7 @@ export const component: ClosureComponent = (): Component => {
const faults = store.fetch("faults", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("faults", filter);
@@ -165,13 +167,13 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("faults", 3)) {
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return m(
"button.primary",
{
disabled: selected.size === 0,
title: "Delete selected faults",
onclick: e => {
onclick: (e) => {
e.redraw = false;
e.target.disabled = true;
@@ -179,22 +181,22 @@ export const component: ClosureComponent = (): Component => {
return;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("faults", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} faults deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
);
@@ -209,8 +211,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing faults"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+31 -29
View File
@@ -51,23 +51,23 @@ const attributes: {
"2 Web Content",
"3 Vendor Configuration File",
"4 Tone File",
"5 Ringer File"
]
"5 Ringer File",
],
},
{ id: "metadata.oui", label: "OUI" },
{ id: "metadata.productClass", label: "Product Class" },
{ id: "metadata.version", label: "Version" }
{ id: "metadata.version", label: "Version" },
];
const formData = {
resource: "files",
attributes: attributes
.slice(1) // remove _id from new object form
.concat([{ id: "file", label: "File", type: "file" }])
.concat([{ id: "file", label: "File", type: "file" }]),
};
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("files", e[2], e[3]);
return e;
@@ -91,7 +91,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store
.resourceExists("files", id)
.then(exists => {
.then((exists) => {
if (exists) {
store.fulfill(0, Date.now());
return void resolve({ file: "File already exists" });
@@ -99,7 +99,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
const headers = Object.assign(
{
"Content-Type": "application/octet-stream",
Accept: "application/octet-stream"
Accept: "application/octet-stream",
},
object
);
@@ -109,8 +109,8 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
method: "PUT",
headers: headers,
url: `api/files/${encodeURIComponent(id)}`,
serialize: body => body, // Identity function to prevent JSON.parse on blob data
body: file
serialize: (body) => body, // Identity function to prevent JSON.parse on blob data
body: file,
})
.then(() => {
notifications.push(
@@ -129,16 +129,18 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
});
}
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/files.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("files", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -152,7 +154,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Files - GenieACS";
function showMore(): void {
@@ -195,7 +197,7 @@ export const component: ClosureComponent = (): Component => {
const files = store.fetch("files", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("files", filter);
@@ -212,7 +214,7 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("files", 3)) {
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -225,9 +227,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -239,19 +241,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -260,7 +262,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected files",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(`Deleting ${selected.size} files. Are you sure?`)
)
@@ -269,25 +271,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("files", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} files deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -300,8 +302,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing files"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+15 -13
View File
@@ -25,16 +25,16 @@ import * as smartQuery from "./smart-query";
import { filterToMongoQuery } from "../lib/mongodb-functions";
import { Expression } from "../lib/types";
const getAutocomplete = memoize(resource => {
const getAutocomplete = memoize((resource) => {
const labels = smartQuery.getLabels(resource);
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
txt = txt.toLowerCase();
cb(
labels
.filter(s => s.toLowerCase().includes(txt))
.map(s => ({
.filter((s) => s.toLowerCase().includes(txt))
.map((s) => ({
value: `${s}: `,
tip: smartQuery.getTip(resource, s)
tip: smartQuery.getTip(resource, s),
}))
);
});
@@ -53,7 +53,7 @@ function parseFilter(resource, f): Expression {
// Throw exception if invalid Mongo query
filterToMongoQuery(
map(exp, e => {
map(exp, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack(resource, e[2], e[3]);
return e;
@@ -69,7 +69,7 @@ function stringifyFilter(f: Expression): string {
return stringify(f);
}
const splitFilter = memoize(filter => {
const splitFilter = memoize((filter) => {
if (!filter) return [""];
const list = [];
const f = parse(filter);
@@ -83,7 +83,7 @@ const splitFilter = memoize(filter => {
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
if (
!vnode.state["filterList"] ||
vnode.attrs["filter"] !== vnode.state["filter"]
@@ -95,7 +95,7 @@ const component: ClosureComponent = (): Component => {
function onChange(): void {
vnode.state["filterInvalid"] = 0;
vnode.state["filterList"] = vnode.state["filterList"].filter(f => f);
vnode.state["filterList"] = vnode.state["filterList"].filter((f) => f);
let filter = vnode.state["filterList"].map((f, idx) => {
try {
return parseFilter(vnode.attrs["resource"], f);
@@ -128,19 +128,21 @@ const component: ClosureComponent = (): Component => {
(vnode.state["filterInvalid"] >> idx) & 1 ? "error" : ""
}`,
value: fltr,
onchange: e => {
onchange: (e) => {
vnode.state["filterList"] = vnode.state["filterList"].slice();
vnode.state["filterList"][idx] = e.target.value.trim();
onChange();
},
oncreate: vn => {
getAutocomplete(vnode.attrs["resource"]).attach(vn.dom);
}
oncreate: (vn) => {
getAutocomplete(vnode.attrs["resource"]).attach(
vn.dom as HTMLInputElement
);
},
});
})
)
);
}
},
};
};
+12 -12
View File
@@ -57,14 +57,14 @@ function renderTable(
const selectAll = m("input", {
type: "checkbox",
checked: records.length && selected.size === records.length,
onchange: e => {
onchange: (e) => {
for (const record of records) {
const id = record["_id"] || record["DeviceID.ID"].value[0];
if (e.target.checked) selected.add(id);
else selected.delete(id);
}
},
disabled: !total
disabled: !total,
});
labels.push(m("th", selectAll));
}
@@ -89,7 +89,7 @@ function renderTable(
onclick: () => {
if (sortAttributes[i] > 0) direction *= -1;
return onSortChange({ [i]: direction });
}
},
},
symbol
);
@@ -106,14 +106,14 @@ function renderTable(
const checkbox = m("input", {
type: "checkbox",
checked: selected.has(id),
onchange: e => {
onchange: (e) => {
if (e.target.checked) selected.add(id);
else selected.delete(id);
},
onclick: e => {
onclick: (e) => {
e.stopPropagation();
e.redraw = false;
}
},
});
tds.push(m("td", checkbox));
}
@@ -152,14 +152,14 @@ function renderTable(
m(
"tr",
{
onclick: e => {
onclick: (e) => {
if (["INPUT", "BUTTON", "A"].includes(e.target.nodeName)) {
e.redraw = false;
return;
}
if (!selected.delete(id)) selected.add(id);
}
},
},
tds
)
@@ -180,7 +180,7 @@ function renderTable(
{
title: "Show more records",
onclick: showMoreCallback,
disabled: !data.length || records.length >= total
disabled: !data.length || records.length >= total,
},
"More"
)
@@ -203,7 +203,7 @@ function renderTable(
m("thead", m("tr", labels)),
m("tbody", rows),
tfoot
)
),
];
if (buttons.length) children.push(m("div.actions-bar", buttons));
@@ -214,7 +214,7 @@ const component: ClosureComponent = (): Component => {
let selected = new Set<string>();
return {
view: vnode => {
view: (vnode) => {
const attributes = vnode.attrs["attributes"];
const data = vnode.attrs["data"];
const valueCallback = vnode.attrs["valueCallback"];
@@ -246,7 +246,7 @@ const component: ClosureComponent = (): Component => {
actionsCallback,
recordActionsCallback
);
}
},
};
};
+6 -6
View File
@@ -31,12 +31,12 @@ const adminPages = [
"files",
"config",
"users",
"permissions"
"permissions",
];
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
let sideMenu, group;
if (adminPages.includes(vnode.attrs["page"])) {
@@ -54,18 +54,18 @@ const component: ClosureComponent = (): Component => {
m("img.logo", { src: "logo.svg" }),
m(userMenu),
m(menu, attrs),
m(drawerComponent)
m(drawerComponent),
]),
m(
"#content-wrapper",
sideMenu,
m("#content", { class: `page-${vnode.attrs["page"]}` }, [
vnode.children
vnode.children,
])
),
overlay.render()
overlay.render(),
];
}
},
};
};
+16 -14
View File
@@ -24,13 +24,15 @@ import * as notifications from "./notifications";
import * as overlay from "./overlay";
import changePasswordComponent from "./change-password-component";
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
return Promise.resolve(args);
}
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
if (window.username) m.route.set(vnode.attrs["continue"] || "/");
document.title = "Login - GenieACS";
@@ -45,12 +47,12 @@ export const component: ClosureComponent = (): Component => {
name: "username",
type: "text",
value: vnode.state["username"],
oncreate: vnode2 => {
oncreate: (vnode2) => {
(vnode2.dom as HTMLInputElement).focus();
},
oninput: e => {
oninput: (e) => {
vnode.state["username"] = e.target.value;
}
},
})
),
m(
@@ -60,9 +62,9 @@ export const component: ClosureComponent = (): Component => {
name: "password",
type: "password",
value: vnode.state["password"],
oninput: e => {
oninput: (e) => {
vnode.state["password"] = e.target.value;
}
},
})
),
m(
@@ -71,19 +73,19 @@ export const component: ClosureComponent = (): Component => {
"button.primary",
{
type: "submit",
onclick: e => {
onclick: (e) => {
e.target.disabled = true;
store
.logIn(vnode.state["username"], vnode.state["password"])
.then(() => {
location.reload();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
e.target.disabled = false;
});
return false;
}
},
},
"Login"
)
@@ -98,16 +100,16 @@ export const component: ClosureComponent = (): Component => {
onPasswordChange: () => {
overlay.close(cb);
m.redraw();
}
},
};
return m(changePasswordComponent, attrs);
};
overlay.open(cb);
}
},
},
"Change password"
)
),
];
}
},
};
};
+8 -8
View File
@@ -22,23 +22,23 @@ import * as overlay from "./overlay";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const text = vnode.attrs["text"];
const element = vnode.attrs["element"] || "span";
function overflowed(_vnode): void {
_vnode.dom.classList.add("long-text-overflowed");
_vnode.dom.onclick = e => {
_vnode.dom.onclick = (e) => {
overlay.open(() => {
return m("textarea.long-text", {
value: text,
cols: 80,
rows: 24,
readonly: "",
oncreate: vnode2 => {
oncreate: (vnode2) => {
(vnode2.dom as HTMLTextAreaElement).focus();
(vnode2.dom as HTMLTextAreaElement).select();
}
},
});
});
// prevent index page selection
@@ -50,11 +50,11 @@ const component: ClosureComponent = (): Component => {
return m(
element,
{
oncreate: vnode2 => {
oncreate: (vnode2) => {
if (vnode2.dom.clientWidth !== vnode2.dom.scrollWidth)
overflowed(vnode2);
},
onupdate: vnode2 => {
onupdate: (vnode2) => {
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
(vnode2.dom as HTMLElement).classList.remove(
"long-text-overflowed"
@@ -65,11 +65,11 @@ const component: ClosureComponent = (): Component => {
}
},
class: "long-text",
title: text
title: text,
},
text
);
}
},
};
};
+3 -3
View File
@@ -21,7 +21,7 @@ import m, { ClosureComponent, Component } from "mithril";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const active = { [vnode.attrs["page"]]: "active" };
const tabs = [];
@@ -59,7 +59,7 @@ const component: ClosureComponent = (): Component => {
"presets",
"provisions",
"virtualParameters",
"files"
"files",
];
for (const page of adminPages) {
if (window.authorizer.hasAccess(page, 2)) {
@@ -75,7 +75,7 @@ const component: ClosureComponent = (): Component => {
}
return m("nav", m("ul", tabs));
}
},
};
};
+6 -2
View File
@@ -28,12 +28,16 @@ interface Notification {
const notifications = new Set<Notification>();
export function push(type, message, actions?): Notification {
export function push(
type: string,
message: string,
actions?: { [label: string]: () => void }
): Notification {
const n: Notification = {
type: type,
message: message,
timestamp: Date.now(),
actions: actions
actions: actions,
};
notifications.add(n);
m.redraw();
+7 -7
View File
@@ -46,25 +46,25 @@ export function render(): Children {
close(overlayCallback);
},
style: "opacity: 0",
oncreate: vnode => {
oncreate: (vnode) => {
(vnode.dom as HTMLDivElement).focus();
(vnode.dom as HTMLDivElement).style.opacity = "1";
},
onbeforeremove: vnode => {
onbeforeremove: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "0";
return new Promise(resolve => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 500);
});
}
},
},
m(
".overlay",
{
onclick: e => {
onclick: (e) => {
e.stopPropagation();
}
},
},
overlayCallback()
)
@@ -74,7 +74,7 @@ export function render(): Children {
return null;
}
document.addEventListener("keydown", e => {
document.addEventListener("keydown", (e) => {
if (overlayCallback && e.keyCode === 27 && close(overlayCallback)) m.redraw();
});
+4 -4
View File
@@ -30,7 +30,7 @@ for (const group of Object.values(GROUPS)) {
CHARTS[chartName] = config.ui.overview.charts[chartName];
}
function queryCharts(charts): {} {
function queryCharts(charts: Record<string, unknown>): Record<string, unknown> {
charts = Object.assign({}, charts);
for (let [chartName, chart] of Object.entries(charts)) {
charts[chartName] = chart = Object.assign({}, chart);
@@ -44,7 +44,7 @@ function queryCharts(charts): {} {
return charts;
}
export function init(): Promise<{}> {
export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 1)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -56,7 +56,7 @@ export function init(): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Overview - GenieACS";
const children = [];
for (const group of Object.values(GROUPS)) {
@@ -79,6 +79,6 @@ export const component: ClosureComponent = (): Component => {
}
return children;
}
},
};
};
+36 -34
View File
@@ -51,21 +51,21 @@ const attributes = [
"users",
"presets",
"provisions",
"virtualParameters"
]
"virtualParameters",
],
},
{ id: "filter", label: "Filter", type: "textarea" },
{
id: "access",
label: "Access",
type: "combo",
options: ["1: count", "2: read", "3: write"]
options: ["1: count", "2: read", "3: write"],
},
{ id: "validate", label: "Validate", type: "textarea" }
{ id: "validate", label: "Validate", type: "textarea" },
];
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("permissions", e[2], e[3]);
return e;
@@ -96,7 +96,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
object.filter = stringify(memoizedParse(object.filter));
} catch (err) {
return void resolve({
filter: "Filter must be valid expression"
filter: "Filter must be valid expression",
});
}
}
@@ -106,7 +106,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
object.validate = stringify(memoizedParse(object.validate));
} catch (err) {
return void resolve({
validate: "Validate must be valid expression"
validate: "Validate must be valid expression",
});
}
}
@@ -115,7 +115,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store
.resourceExists("permissions", id)
.then(exists => {
.then((exists) => {
if (exists) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Permission already exists" });
@@ -139,7 +139,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
store.fulfill(0, Date.now());
reject(err);
});
@@ -151,19 +151,21 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
const formData = {
resource: "permissions",
attributes: attributes
attributes: attributes,
};
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/permissions.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("permissions", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -176,7 +178,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Permissions - GenieACS";
function showMore(): void {
@@ -222,7 +224,7 @@ export const component: ClosureComponent = (): Component => {
const permissions = store.fetch("permissions", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("permissions", filter);
@@ -252,7 +254,7 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("permissions", 3)) {
attrs["recordActionsCallback"] = permission => {
attrs["recordActionsCallback"] = (permission) => {
return [
m(
"button",
@@ -266,17 +268,17 @@ export const component: ClosureComponent = (): Component => {
)
return;
putActionHandler("delete", permission).catch(err => {
putActionHandler("delete", permission).catch((err) => {
notifications.push("error", err.message);
});
}
},
},
getIcon("remove")
)
),
];
};
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -289,9 +291,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -303,19 +305,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -324,7 +326,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected permissions",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(
`Deleting ${selected.size} permissions. Are you sure?`
@@ -335,25 +337,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("permissions", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} permissions deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -366,8 +368,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing permissions"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+14 -14
View File
@@ -43,19 +43,19 @@ function drawChart(chartData): Children {
legend.push(
m(".legend-line", [
m("span.color", {
style: `background-color: ${slice["color"]} !important;`
style: `background-color: ${slice["color"]} !important;`,
}),
`${slice["label"]}: `,
m(
"a",
{
href: `#!/devices/?${m.buildQueryString({
filter: memoizedStringify(slice["filter"])
})}`
filter: memoizedStringify(slice["filter"]),
})}`,
},
slice["count"]["value"] || 0
),
` (${(percent * 100).toFixed(2)}%)`
` (${(percent * 100).toFixed(2)}%)`,
])
);
@@ -76,7 +76,7 @@ function drawChart(chartData): Children {
paths.push(
m("path", {
d: sketch,
fill: slice["color"]
fill: slice["color"],
})
);
@@ -90,13 +90,13 @@ function drawChart(chartData): Children {
"a",
{
"xlink:href": `#!/devices/?${m.buildQueryString({
filter: memoizedStringify(slice["filter"])
})}`
filter: memoizedStringify(slice["filter"]),
})}`,
},
[
m("path", {
d: sketch,
"fill-opacity": 0
"fill-opacity": 0,
}),
m(
"text",
@@ -104,10 +104,10 @@ function drawChart(chartData): Children {
x: percentageX,
y: percentageY,
"dominant-baseline": "middle",
"text-anchor": "middle"
"text-anchor": "middle",
},
`${(percent * 100).toFixed(2)}%`
)
),
]
)
);
@@ -125,19 +125,19 @@ function drawChart(chartData): Children {
width: "204px",
height: "204px",
xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
"xmlns:xlink": "http://www.w3.org/1999/xlink",
},
paths.concat(links)
),
m(".legend", legend)
m(".legend", legend),
]);
}
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
return drawChart(vnode.attrs["chart"]);
}
},
};
};
+42 -40
View File
@@ -17,7 +17,7 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClosureComponent, Component, Children } from "mithril";
import { ClosureComponent, Component, Children, Vnode } from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
@@ -43,11 +43,11 @@ const attributes = [
{ id: "events", label: "Events" },
{ id: "precondition", label: "Precondition", type: "textarea" },
{ id: "provision", label: "Provision", type: "combo" },
{ id: "provisionArgs", label: "Arguments" }
{ id: "provisionArgs", label: "Arguments" },
];
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("presets", e[2], e[3]);
return e;
@@ -77,14 +77,14 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
object.precondition = stringify(memoizedParse(object.precondition));
} catch (err) {
return void resolve({
precondition: "Precondition must be valid expression"
precondition: "Precondition must be valid expression",
});
}
}
store
.resourceExists("presets", id)
.then(exists => {
.then((exists) => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Preset already exists" });
@@ -116,7 +116,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
reject(err);
store.fulfill(0, Date.now());
});
@@ -128,19 +128,21 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const formData = {
resource: "presets",
attributes: attributes
attributes: attributes,
};
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/presets.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("presets", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -154,7 +156,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Presets - GenieACS";
function showMore(): void {
@@ -207,7 +209,7 @@ export const component: ClosureComponent = (): Component => {
const presets = store.fetch("presets", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("presets", filter);
@@ -220,7 +222,7 @@ export const component: ClosureComponent = (): Component => {
"reboot",
"reset",
"download",
"instances"
"instances",
]);
const provisions = store.fetch("provisions", true);
@@ -231,19 +233,19 @@ export const component: ClosureComponent = (): Component => {
}
}
const provisionAttr = attributes.find(attr => {
const provisionAttr = attributes.find((attr) => {
return attr.id === "provision";
});
provisionAttr["options"] = Array.from(provisionIds);
const downloadUrl = getDownloadUrl(filter);
const valueCallback = (attr, preset): {} => {
const valueCallback = (attr, preset): Vnode => {
if (attr.id === "precondition") {
let devicesUrl = "#!/devices";
if (preset["precondition"].length) {
devicesUrl += `?${m.buildQueryString({
filter: preset["precondition"]
filter: preset["precondition"],
})}`;
}
@@ -260,8 +262,8 @@ export const component: ClosureComponent = (): Component => {
"a",
{
href: `#!/admin/provisions?${m.buildQueryString({
filter: `Q("ID", "${preset["provision"]}")`
})}`
filter: `Q("ID", "${preset["provision"]}")`,
})}`,
},
preset["provision"]
);
@@ -279,7 +281,7 @@ export const component: ClosureComponent = (): Component => {
attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl;
attrs["valueCallback"] = valueCallback;
attrs["recordActionsCallback"] = preset => {
attrs["recordActionsCallback"] = (preset) => {
return [
m(
"a",
@@ -300,9 +302,9 @@ export const component: ClosureComponent = (): Component => {
{
base: preset,
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, false)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -314,27 +316,27 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"Show"
)
),
];
};
if (window.authorizer.hasAccess("presets", 3)) {
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -347,9 +349,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, true)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -361,19 +363,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -382,7 +384,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected presets",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(`Deleting ${selected.size} presets. Are you sure?`)
)
@@ -391,25 +393,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("presets", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} presets deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -422,8 +424,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing presets"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+34 -32
View File
@@ -38,11 +38,11 @@ const memoizedJsonParse = memoize(JSON.parse);
const attributes = [
{ id: "_id", label: "Name" },
{ id: "script", label: "Script", type: "code" }
{ id: "script", label: "Script", type: "code" },
];
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("provisions", e[2], e[3]);
return e;
@@ -64,7 +64,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store
.resourceExists("provisions", id)
.then(exists => {
.then((exists) => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Provision already exists" });
@@ -96,7 +96,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
store.fulfill(0, Date.now());
reject(err);
});
@@ -108,19 +108,21 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const formData = {
resource: "provisions",
attributes: attributes
attributes: attributes,
};
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/provisions.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("provisions", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -141,7 +143,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Provisions - GenieACS";
function showMore(): void {
@@ -184,7 +186,7 @@ export const component: ClosureComponent = (): Component => {
const provisions = store.fetch("provisions", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("provisions", filter);
@@ -199,7 +201,7 @@ export const component: ClosureComponent = (): Component => {
attrs["sortAttributes"] = sortAttributes;
attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl;
attrs["recordActionsCallback"] = provision => {
attrs["recordActionsCallback"] = (provision) => {
return [
m(
"a",
@@ -212,9 +214,9 @@ export const component: ClosureComponent = (): Component => {
{
base: provision,
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, false)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -226,27 +228,27 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"Show"
)
),
];
};
if (window.authorizer.hasAccess("provisions", 3)) {
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -259,9 +261,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, true)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -273,19 +275,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -294,7 +296,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected provisions",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(
`Deleting ${selected.size} provisions. Are you sure?`
@@ -305,25 +307,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("provisions", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} provisions deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -336,8 +338,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing provisions"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+47 -44
View File
@@ -27,7 +27,7 @@ const singular = {
virtualParameters: "virtual parameter",
files: "file",
users: "user",
permissions: "permission"
permissions: "permission",
};
function createField(current, attr, focus): Children {
@@ -50,14 +50,14 @@ function createField(current, attr, focus): Children {
name: attr.id,
value: selected,
oncreate: focus
? _vnode => {
? (_vnode) => {
(_vnode.dom as HTMLSelectElement).focus();
}
: null,
onchange: e => {
onchange: (e) => {
current.object[attr.id] = e.target.value;
e.redraw = false;
}
},
},
options
);
@@ -66,22 +66,22 @@ function createField(current, attr, focus): Children {
new Set(attr.options.concat(current.object[attr.id] || []))
);
const currentSelected = new Set(current.object[attr.id]);
const options = optionsValues.map(op => {
const options = optionsValues.map((op) => {
const id = `${attr.id}-${op}`;
const opts = {
type: "checkbox",
id: id,
value: op,
oncreate: _vnode => {
oncreate: (_vnode) => {
if (focus && !options.length) _vnode.dom.focus();
if (currentSelected.has(op)) _vnode.dom.checked = true;
},
onchange: e => {
onchange: (e) => {
if (e.target.checked) currentSelected.add(op);
else currentSelected.delete(op);
current.object[attr.id] = Array.from(currentSelected);
e.redraw = false;
}
},
};
return m("tr", [m("td", m("input", opts)), m("td", op)]);
@@ -93,12 +93,12 @@ function createField(current, attr, focus): Children {
id: attr.id,
value: current.object[attr.id],
mode: "javascript",
onSubmit: dom => {
onSubmit: (dom) => {
dom.form.querySelector("button[type=submit]").click();
},
onChange: value => {
onChange: (value) => {
current.object[attr.id] = value;
}
},
};
return m(codeEditorComponent, attrs);
} else if (attr.type === "file") {
@@ -106,14 +106,14 @@ function createField(current, attr, focus): Children {
type: "file",
name: attr.id,
oncreate: focus
? _vnode => {
? (_vnode) => {
(_vnode.dom as HTMLInputElement).focus();
}
: null,
onchange: e => {
onchange: (e) => {
current.object[attr.id] = e.target.files;
e.redraw = false;
}
},
});
} else if (attr.type === "textarea") {
return m("textarea", {
@@ -124,17 +124,17 @@ function createField(current, attr, focus): Children {
rows: attr.rows || 4,
style: "resize: none;",
oncreate: focus
? _vnode => {
? (_vnode) => {
const dom = _vnode.dom as HTMLInputElement;
dom.focus();
dom.setSelectionRange(dom.value.length, dom.value.length);
}
: null,
oninput: e => {
oninput: (e) => {
current.object[attr.id] = e.target.value;
e.redraw = false;
},
onkeypress: e => {
onkeypress: (e) => {
e.redraw = false;
if (e.which === 13 && !e.shiftKey) {
const dom = e.target;
@@ -142,7 +142,7 @@ function createField(current, attr, focus): Children {
return false;
}
return true;
}
},
});
}
@@ -152,20 +152,20 @@ function createField(current, attr, focus): Children {
disabled: attr.id === "_id" && !current.isNew,
value: current.object[attr.id],
oncreate: focus
? _vnode => {
? (_vnode) => {
(_vnode.dom as HTMLInputElement).focus();
}
: null,
oninput: e => {
oninput: (e) => {
current.object[attr.id] = e.target.value;
e.redraw = false;
}
},
});
}
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const actionHandler = vnode.attrs["actionHandler"];
const attributes = vnode.attrs["attributes"];
const resource = vnode.attrs["resource"];
@@ -173,7 +173,7 @@ const component: ClosureComponent = (): Component => {
if (!vnode.state["current"]) {
vnode.state["current"] = {
isNew: !base["_id"],
object: Object.assign({}, base)
object: Object.assign({}, base),
};
}
@@ -204,21 +204,23 @@ const component: ClosureComponent = (): Component => {
const buttons = [submit];
if (!current.isNew) {
buttons.push(m(
"button.primary",
{
type: "button",
title: `Delete ${singular[resource] || resource}`,
onclick: e => {
e.redraw = false;
e.target.disabled = true;
actionHandler("delete", current.object).then(() => {
e.target.disabled = false;
});
}
},
"Delete"
) as VnodeDOM);
buttons.push(
m(
"button.primary",
{
type: "button",
title: `Delete ${singular[resource] || resource}`,
onclick: (e) => {
e.redraw = false;
e.target.disabled = true;
actionHandler("delete", current.object).then(() => {
e.target.disabled = false;
});
},
},
"Delete"
) as VnodeDOM
);
}
form.push(m(".actions-bar", buttons));
@@ -226,13 +228,14 @@ const component: ClosureComponent = (): Component => {
const children = [
m(
"h1",
`${current.isNew ? "New" : "Editing"} ${singular[resource] ||
resource}`
`${current.isNew ? "New" : "Editing"} ${
singular[resource] || resource
}`
),
m(
"form",
{
onsubmit: e => {
onsubmit: (e) => {
e.redraw = false;
// const onsubmit = e.target.onsubmit;
e.preventDefault();
@@ -244,14 +247,14 @@ const component: ClosureComponent = (): Component => {
// e.target.onsubmit = onsubmit;
(submit.dom as HTMLFormElement).disabled = false;
});
}
},
},
form
)
),
];
return m("div.put-form", children);
}
},
};
};
+30 -21
View File
@@ -27,18 +27,18 @@ const resources = {
Channel: { parameter: ["PARAM", "channel"], type: "string" },
Code: { parameter: ["PARAM", "code"], type: "string" },
Retries: { parameter: ["PARAM", "retries"], type: "number" },
Timestamp: { parameter: ["PARAM", "timestamp"], type: "timestamp" }
Timestamp: { parameter: ["PARAM", "timestamp"], type: "timestamp" },
},
presets: {
ID: { parameter: ["PARAM", "_id"], type: "string" },
Channel: { parameter: ["PARAM", "channel"], type: "string" },
Weight: { parameter: ["PARAM", "weight"], type: "number" }
Weight: { parameter: ["PARAM", "weight"], type: "number" },
},
provisions: {
ID: { parameter: ["PARAM", "_id"], type: "string" }
ID: { parameter: ["PARAM", "_id"], type: "string" },
},
virtualParameters: {
ID: { parameter: ["PARAM", "_id"], type: "string" }
ID: { parameter: ["PARAM", "_id"], type: "string" },
},
files: {
ID: { parameter: ["PARAM", "_id"], type: "string" },
@@ -46,35 +46,36 @@ const resources = {
OUI: { parameter: ["PARAM", "metadata.oui"], type: "string" },
"Product class": {
parameter: ["PARAM", "metadata.productClass"],
type: "string"
type: "string",
},
Version: { parameter: ["PARAM", "metadata.version"], type: "string" }
Version: { parameter: ["PARAM", "metadata.version"], type: "string" },
},
permissions: {
Role: { parameter: ["PARAM", "role"], type: "string" },
Resource: { parameter: ["PARAM", "resource"], type: "string" },
Access: { parameter: ["PARAM", "access"], type: "number" }
Access: { parameter: ["PARAM", "access"], type: "number" },
},
users: { Username: { parameter: ["PARAM", "_id"], type: "string" } }
users: { Username: { parameter: ["PARAM", "_id"], type: "string" } },
};
for (const v of Object.values(config.ui.filters as {
label: string;
parameter: string;
type: string;
}[])) {
for (const v of Object.values(
config.ui.filters as Record<
string,
{ label: string; parameter: string; type: string }
>
)) {
resources.devices[v.label] = {
parameter: v.parameter,
type: (v.type || "").split(",").map(s => s.trim())
type: (v.type || "").split(",").map((s) => s.trim()),
};
}
export function getLabels(resource): string[] {
export function getLabels(resource: string): string[] {
if (!resources[resource]) return [];
return Object.keys(resources[resource]);
}
function queryNumber(param, value): Expression {
function queryNumber(param: string, value: string): Expression {
let op = "=";
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
if (value.startsWith(o)) {
@@ -117,7 +118,7 @@ function queryMac(param, value): Expression {
return [
"LIKE",
["FUNC", "LOWER", param],
value.replace(/(..)(?!$)/g, "$1:")
value.replace(/(..)(?!$)/g, "$1:"),
];
}
@@ -126,9 +127,13 @@ function queryMac(param, value): Expression {
[
"LIKE",
["FUNC", "LOWER", param],
`%${value.replace(/(..)(?!$)/g, "$1:")}%`
`%${value.replace(/(..)(?!$)/g, "$1:")}%`,
],
[
"LIKE",
["FUNC", "LOWER", param],
`%${value.replace(/(.)(.)/g, "$1:$2")}%`,
],
["LIKE", ["FUNC", "LOWER", param], `%${value.replace(/(.)(.)/g, "$1:$2")}%`]
];
}
@@ -137,7 +142,7 @@ function queryTag(tag: string): Expression {
return ["IS NOT NULL", ["PARAM", `Tags.${t}`]];
}
export function getTip(resource, label): string {
export function getTip(resource: string, label: string): string {
let tip;
if (resources[resource] && resources[resource][label]) {
const param = resources[resource][label];
@@ -172,7 +177,11 @@ export function getTip(resource, label): string {
return tip;
}
export function unpack(resource, label, value): Expression {
export function unpack(
resource: string,
label: string,
value: string
): Expression {
if (!resources[resource]) return null;
const type = resources[resource][label].type;
value = value.trim();
+106 -90
View File
@@ -24,6 +24,8 @@ import memoize from "../lib/common/memoize";
import { QueryOptions, Expression } from "../lib/types";
import * as notifications from "./notifications";
import { configSnapshot, genieacsVersion } from "./config";
import { QueueTask } from "./task-queue";
import { PingResult } from "../lib/ping";
const memoizedStringify = memoize(stringify);
const memoizedEvaluate = memoize(evaluate);
@@ -39,7 +41,7 @@ const queries = {
fulfilled: new WeakMap(),
fulfilling: new WeakSet(),
accessed: new WeakMap(),
value: new WeakMap()
value: new WeakMap(),
};
interface Resources {
@@ -62,13 +64,13 @@ for (const r of [
"files",
"config",
"users",
"permissions"
"permissions",
]) {
resources[r] = {
objects: new Map(),
count: new Map(),
fetch: new Map(),
combinedFilter: null
combinedFilter: null,
};
}
@@ -94,7 +96,7 @@ function checkConnection(): void {
url: "status",
method: "GET",
background: true,
extract: xhr => {
extract: (xhr) => {
if (xhr.status !== 200) {
if (!connectionNotification) {
connectionNotification = notifications.push(
@@ -125,7 +127,7 @@ function checkConnection(): void {
{
Reload: () => {
window.location.reload();
}
},
}
);
}
@@ -142,27 +144,27 @@ function checkConnection(): void {
{
Reload: () => {
window.location.reload();
}
},
}
);
}
}
}
}
},
});
}
setInterval(checkConnection, 3000);
export async function xhrRequest(
options: { url: string } & m.RequestOptions<{}>
options: { url: string } & m.RequestOptions<unknown>
): Promise<any> {
const extract = options.extract;
const deserialize = options.deserialize;
options.extract = (
xhr: XMLHttpRequest,
_options?: { url: string } & m.RequestOptions<{}>
_options?: { url: string } & m.RequestOptions<unknown>
): any => {
if (typeof extract === "function") return extract(xhr, _options);
@@ -196,13 +198,13 @@ export async function xhrRequest(
return m.request(options);
}
export function unpackExpression(exp): Expression {
export function unpackExpression(exp: Expression): Expression {
if (!Array.isArray(exp)) return exp;
const e = memoizedEvaluate(exp, null, fulfillTimestamp);
return e;
}
export function count(resourceType, filter): QueryResponse {
export function count(resourceType: string, filter: Expression): QueryResponse {
const filterStr = memoizedStringify(filter);
let queryResponse = resources[resourceType].count.get(filterStr);
if (queryResponse) return queryResponse;
@@ -216,45 +218,40 @@ export function count(resourceType, filter): QueryResponse {
function limitFilter(filter, sort, bookmark): Expression {
const sortSort = (a, b): number => Math.abs(b[1]) - Math.abs(a[1]);
const arr = Object.entries(sort)
.sort(sortSort)
.reverse();
const arr = Object.entries(sort).sort(sortSort).reverse();
return and(
filter,
arr.reduce(
(cur, kv) => {
const [param, asc] = kv;
if (asc <= 0) {
if (bookmark[param] == null) {
return or(
["IS NOT NULL", ["PARAM", param]],
and(["IS NULL", ["PARAM", param]], cur)
);
}
let f = null;
if (typeof bookmark[param] !== "string")
f = or(f, [">=", ["PARAM", param], ""]);
f = or(f, [">", ["PARAM", param], bookmark[param]]);
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
} else {
let f: Expression = ["IS NULL", ["PARAM", param]];
if (bookmark[param] == null) return and(f, cur);
if (typeof bookmark[param] !== "number") {
f = or(f, [">=", ["PARAM", param], 0]);
f = or(f, ["<", ["PARAM", param], 0]);
}
f = or(f, ["<", ["PARAM", param], bookmark[param]]);
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
arr.reduce((cur, kv) => {
const [param, asc] = kv;
if (asc <= 0) {
if (bookmark[param] == null) {
return or(
["IS NOT NULL", ["PARAM", param]],
and(["IS NULL", ["PARAM", param]], cur)
);
}
},
true as Expression
)
let f = null;
if (typeof bookmark[param] !== "string")
f = or(f, [">=", ["PARAM", param], ""]);
f = or(f, [">", ["PARAM", param], bookmark[param]]);
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
} else {
let f: Expression = ["IS NULL", ["PARAM", param]];
if (bookmark[param] == null) return and(f, cur);
if (typeof bookmark[param] !== "number") {
f = or(f, [">=", ["PARAM", param], 0]);
f = or(f, ["<", ["PARAM", param], 0]);
}
f = or(f, ["<", ["PARAM", param], bookmark[param]]);
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
}
}, true as Expression)
);
}
@@ -287,7 +284,7 @@ function compareFunction(sort: {
const w = {
null: 1,
number: 2,
string: 3
string: 3,
};
const w1 = w[v1 == null ? "null" : typeof v1] || 4;
const w2 = w[v2 == null ? "null" : typeof v2] || 4;
@@ -331,8 +328,8 @@ function inferQuery(resourceType, queryResponse): void {
}
export function fetch(
resourceType,
filter,
resourceType: string,
filter: Expression,
options: QueryOptions = {}
): QueryResponse {
const filterStr = memoizedStringify(filter);
@@ -357,7 +354,10 @@ export function fetch(
return queryResponse;
}
export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
export function fulfill(
accessTimestamp: number,
_fulfillTimestamp: number
): Promise<boolean> {
let updated = false;
if (_fulfillTimestamp > fulfillTimestamp) {
@@ -389,18 +389,18 @@ export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
url:
`api/${resourceType}/?` +
m.buildQueryString({
filter: memoizedStringify(filter)
filter: memoizedStringify(filter),
}),
extract: xhr => +xhr.getResponseHeader("x-total-count"),
background: true
extract: (xhr) => +xhr.getResponseHeader("x-total-count"),
background: true,
})
.then(c => {
.then((c) => {
queries.value.set(queryResponse, c);
queries.fulfilled.set(queryResponse, fulfillTimestamp);
queries.fulfilling.delete(queryResponse);
resolve();
})
.catch(err => reject(err));
.catch((err) => reject(err));
})
);
}
@@ -439,11 +439,11 @@ export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
limit: 1,
skip: limit - 1,
sort: JSON.stringify(sort),
projection: Object.keys(sort).join(",")
})
projection: Object.keys(sort).join(","),
}),
})
.then(res => {
if ((res as {}[]).length) {
.then((res) => {
if ((res as any[]).length) {
// Generate bookmark object
const bm = Object.keys(sort).reduce((b, k) => {
if (res[0][k] != null) {
@@ -478,7 +478,7 @@ export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
for (let [resourceType, toFetch] of Object.entries(toFetchAll)) {
let combinedFilter = null;
toFetch = toFetch.filter(queryResponse => {
toFetch = toFetch.filter((queryResponse) => {
let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter);
const limit = queries.limit.get(queryResponse);
@@ -528,11 +528,11 @@ export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
url:
`api/${resourceType}/?` +
m.buildQueryString({
filter: memoizedStringify(combinedFilterDiff)
})
filter: memoizedStringify(combinedFilterDiff),
}),
})
.then(res => {
for (const r of res as {}[]) {
.then((res) => {
for (const r of res as any[]) {
const id =
resourceType === "devices"
? r["DeviceID.ID"].value[0]
@@ -580,7 +580,10 @@ export function getTimestamp(): number {
return fulfillTimestamp;
}
export function postTasks(deviceId, tasks): Promise<string> {
export function postTasks(
deviceId: string,
tasks: QueueTask[]
): Promise<string> {
for (const t of tasks) {
t.status = "pending";
t.device = deviceId;
@@ -590,7 +593,7 @@ export function postTasks(deviceId, tasks): Promise<string> {
method: "POST",
url: `api/devices/${encodeURIComponent(deviceId)}/tasks`,
body: tasks,
extract: xhr => {
extract: (xhr) => {
if (xhr.status !== 200) throw new Error(xhr.response);
const connectionRequestStatus = xhr.getResponseHeader(
"Connection-Request"
@@ -599,35 +602,45 @@ export function postTasks(deviceId, tasks): Promise<string> {
for (const [i, t] of st.entries()) {
tasks[i]._id = t._id;
tasks[i].status = t.status;
tasks[i].fault = t.fault;
tasks[i]["fault"] = t.fault;
}
return connectionRequestStatus;
}
},
});
}
export function updateTags(deviceId, tags): Promise<void> {
export function updateTags(
deviceId: string,
tags: Record<string, boolean>
): Promise<void> {
return xhrRequest({
method: "POST",
url: `api/devices/${encodeURIComponent(deviceId)}/tags`,
body: tags
body: tags,
});
}
export function deleteResource(resourceType, id): Promise<void> {
export function deleteResource(
resourceType: string,
id: string
): Promise<void> {
return xhrRequest({
method: "DELETE",
url: `api/${resourceType}/${encodeURIComponent(id)}`
url: `api/${resourceType}/${encodeURIComponent(id)}`,
});
}
export function putResource(resourceType, id, object): Promise<void> {
export function putResource(
resourceType: string,
id: string,
object: Record<string, unknown>
): Promise<void> {
for (const k in object) if (object[k] === undefined) object[k] = null;
return xhrRequest({
method: "PUT",
url: `api/${resourceType}/${encodeURIComponent(id)}`,
body: object
body: object,
});
}
@@ -636,11 +649,11 @@ export function queryConfig(pattern = "%"): Promise<any[]> {
return xhrRequest({
method: "GET",
url: `api/config/?${m.buildQueryString({ filter: filter })}`,
background: true
background: true,
});
}
export function resourceExists(resource, id): Promise<number> {
export function resourceExists(resource: string, id: string): Promise<number> {
const param = resource === "devices" ? "DeviceID.ID" : "_id";
const filter = ["=", ["PARAM", param], id];
return xhrRequest({
@@ -648,22 +661,25 @@ export function resourceExists(resource, id): Promise<number> {
url:
`api/${resource}/?` +
m.buildQueryString({
filter: memoizedStringify(filter)
filter: memoizedStringify(filter),
}),
extract: xhr => +xhr.getResponseHeader("x-total-count"),
background: true
extract: (xhr) => +xhr.getResponseHeader("x-total-count"),
background: true,
});
}
export function evaluateExpression(exp, obj): Expression {
export function evaluateExpression(
exp: Expression,
obj: Record<string, unknown>
): Expression {
if (!Array.isArray(exp)) return exp;
return memoizedEvaluate(exp, obj, fulfillTimestamp);
}
export function changePassword(
username,
newPassword,
authPassword?
username: string,
newPassword: string,
authPassword?: string
): Promise<void> {
const body = { newPassword };
if (authPassword) body["authPassword"] = authPassword;
@@ -671,29 +687,29 @@ export function changePassword(
method: "PUT",
url: `api/users/${username}/password`,
background: true,
body
body,
});
}
export function logIn(username, password): Promise<void> {
export function logIn(username: string, password: string): Promise<void> {
return xhrRequest({
method: "POST",
url: "login",
background: true,
body: { username, password }
body: { username, password },
});
}
export function logOut(): Promise<void> {
return xhrRequest({
method: "POST",
url: "logout"
url: "logout",
});
}
export function ping(host): Promise<{}> {
export function ping(host: string): Promise<PingResult> {
return xhrRequest({
url: `api/ping/${encodeURIComponent(host)}`,
background: true
background: true,
});
}
+26 -12
View File
@@ -18,22 +18,28 @@
*/
import * as store from "./store";
import { Task } from "../lib/types";
const queue = new Set();
const staging = new Set();
export interface QueueTask extends Task {
status?: string;
device?: string;
}
export function queueTask(task): void {
const queue: Set<QueueTask> = new Set();
const staging: Set<QueueTask> = new Set();
export function queueTask(task: QueueTask): void {
staging.delete(task);
task.status = "queued";
queue.add(task);
}
export function deleteTask(task): void {
export function deleteTask(task: QueueTask): void {
staging.delete(task);
queue.delete(task);
}
export function getQueue(): Set<{}> {
export function getQueue(): Set<QueueTask> {
return queue;
}
@@ -41,7 +47,7 @@ export function clear(): void {
queue.clear();
}
export function getStaging(): Set<{}> {
export function getStaging(): Set<QueueTask> {
return staging;
}
@@ -49,15 +55,23 @@ export function clearStaging(): void {
staging.clear();
}
export function stageSpv(task): void {
export function stageSpv(task: QueueTask): void {
staging.add(task);
}
export function stageDownload(task): void {
export function stageDownload(task: QueueTask): void {
staging.add(task);
}
export function commit(tasks, callback): Promise<void> {
export function commit(
tasks: QueueTask[],
callback: (
deviceId: string,
err: Error,
conReqStatus: string,
tasks: QueueTask[]
) => void
): Promise<void> {
const devices: { [deviceId: string]: any[] } = {};
for (const t of tasks) {
devices[t.device] = devices[t.device] || [];
@@ -65,13 +79,13 @@ export function commit(tasks, callback): Promise<void> {
queueTask(t);
}
return new Promise(resolve => {
return new Promise((resolve) => {
let counter = 1;
for (const [deviceId, tasks2] of Object.entries(devices)) {
++counter;
store
.postTasks(deviceId, tasks2)
.then(connectionRequestStatus => {
.then((connectionRequestStatus) => {
for (const t of tasks2) {
if (t.status === "pending") t.status = "stale";
else if (t.status === "done") queue.delete(t);
@@ -79,7 +93,7 @@ export function commit(tasks, callback): Promise<void> {
callback(deviceId, null, connectionRequestStatus, tasks2);
if (--counter === 0) resolve();
})
.catch(err => {
.catch((err) => {
for (const t of tasks2) t.status = "stale";
callback(deviceId, err, null, tasks2);
if (--counter === 0) resolve();
+2 -2
View File
@@ -23,10 +23,10 @@ const UNITS = {
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
second: 1000,
};
export default function timeAgo(dtime): string {
export default function timeAgo(dtime: number): string {
let res = "";
let level = 2;
+20 -13
View File
@@ -50,7 +50,7 @@ function putActionHandler(prefix: string[], dataYaml: string): Promise<any> {
store
.queryConfig(`${prefix.join(".")}.%`)
.then(res => {
.then((res) => {
const current = {};
for (const f of res) current[f._id] = f.value;
@@ -59,8 +59,15 @@ function putActionHandler(prefix: string[], dataYaml: string): Promise<any> {
const promises = [];
for (const obj of diff.add)
promises.push(store.putResource("config", obj._id, obj));
for (const obj of diff.add) {
promises.push(
store.putResource(
"config",
obj._id,
(obj as unknown) as Record<string, unknown>
)
);
}
for (const id of diff.remove)
promises.push(store.deleteResource("config", id));
@@ -80,7 +87,7 @@ function putActionHandler(prefix: string[], dataYaml: string): Promise<any> {
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
const prefix = vnode.attrs["prefix"].split(".");
const name = vnode.attrs["name"];
const data = vnode.attrs["data"];
@@ -97,9 +104,9 @@ const component: ClosureComponent = (): Component => {
config && Object.values(config).length
? yaml.stringify(config, {
schema: "failsafe",
tags: args => {
tags: (args) => {
return args;
}
},
})
: "";
@@ -108,12 +115,12 @@ const component: ClosureComponent = (): Component => {
value: yamlString,
mode: "yaml",
focus: true,
onSubmit: dom => {
onSubmit: (dom) => {
dom.form.querySelector("button[type=submit]").click();
},
onChange: value => {
onChange: (value) => {
vnode.state["updatedYaml"] = value;
}
},
};
const code = m(codeEditorComponent, attrs);
@@ -124,7 +131,7 @@ const component: ClosureComponent = (): Component => {
m(
"form",
{
onsubmit: e => {
onsubmit: (e) => {
e.redraw = false;
e.preventDefault();
if (vnode.state["updatedYaml"] == null)
@@ -133,12 +140,12 @@ const component: ClosureComponent = (): Component => {
putActionHandler(prefix, vnode.state["updatedYaml"])
.then(vnode.attrs["onUpdate"])
.catch(vnode.attrs["onError"]);
}
},
},
[code, m(".actions-bar", [submit])]
)
),
]);
}
},
};
};
+5 -5
View File
@@ -31,7 +31,7 @@ const component: ClosureComponent = (): Component => {
m(
"button",
{
onclick: e => {
onclick: (e) => {
e.target.disabled = true;
store
.logOut()
@@ -39,12 +39,12 @@ const component: ClosureComponent = (): Component => {
location.hash = "";
location.reload();
})
.catch(err => {
.catch((err) => {
e.target.disabled = false;
notifications.push("error", err.message);
});
return false;
}
},
},
"Log out"
)
@@ -56,13 +56,13 @@ const component: ClosureComponent = (): Component => {
"a",
{
href:
"#!/login?" + m.buildQueryString({ continue: m.route.get() })
"#!/login?" + m.buildQueryString({ continue: m.route.get() }),
},
"Log in"
)
);
}
}
},
};
};
+41 -39
View File
@@ -38,11 +38,11 @@ const memoizedJsonParse = memoize(JSON.parse);
const attributes = [
{ id: "_id", label: "Username" },
{ id: "roles", label: "Roles", type: "multi", options: [] }
{ id: "roles", label: "Roles", type: "multi", options: [] },
];
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("users", e[2], e[3]);
return e;
@@ -71,7 +71,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
return void resolve({ password: "Password can not be empty" });
} else if (password !== confirm) {
return void resolve({
confirm: "Confirm password doesn't match password"
confirm: "Confirm password doesn't match password",
});
}
}
@@ -83,7 +83,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store
.resourceExists("users", id)
.then(exists => {
.then((exists) => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "User already exists" });
@@ -123,7 +123,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
store.fulfill(0, Date.now());
reject(err);
});
@@ -133,17 +133,19 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
});
}
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/users.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("users", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -157,7 +159,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Users - GenieACS";
function showMore(): void {
@@ -203,7 +205,7 @@ export const component: ClosureComponent = (): Component => {
const users = store.fetch("users", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("users", filter);
@@ -213,7 +215,7 @@ export const component: ClosureComponent = (): Component => {
if (permissions.fulfilled) {
for (const attr of attributes) {
if (attr.id === "roles")
attr.options = [...new Set(permissions.value.map(p => p.role))];
attr.options = [...new Set(permissions.value.map((p) => p.role))];
}
}
@@ -229,7 +231,7 @@ export const component: ClosureComponent = (): Component => {
attrs["sortAttributes"] = sortAttributes;
attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl;
attrs["recordActionsCallback"] = user => {
attrs["recordActionsCallback"] = (user) => {
return [
m(
"a",
@@ -243,12 +245,12 @@ export const component: ClosureComponent = (): Component => {
{
base: {
_id: user._id,
roles: user.roles.split(",")
roles: user.roles.split(","),
},
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, false)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -260,19 +262,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
{
resource: "users",
attributes: attributes
attributes: attributes,
}
)
)
),
];
if (canWrite) {
@@ -283,7 +285,7 @@ export const component: ClosureComponent = (): Component => {
onPasswordChange: () => {
overlay.close(cb);
m.redraw();
}
},
};
children.push(m(changePasswordComponent, _attrs));
}
@@ -291,10 +293,10 @@ export const component: ClosureComponent = (): Component => {
return children;
};
overlay.open(cb);
}
},
},
"Show"
)
),
];
};
@@ -305,10 +307,10 @@ export const component: ClosureComponent = (): Component => {
attributes[0],
{ id: "password", label: "Password", type: "password" },
{ id: "confirm", label: "Confirm password", type: "password" },
attributes[1]
]
attributes[1],
],
};
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -321,9 +323,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, true)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -335,19 +337,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -356,7 +358,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected users",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(`Deleting ${selected.size} users. Are you sure?`)
)
@@ -365,25 +367,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("users", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} users deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -396,8 +398,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing users"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+34 -32
View File
@@ -38,11 +38,11 @@ const memoizedJsonParse = memoize(JSON.parse);
const attributes = [
{ id: "_id", label: "Name" },
{ id: "script", label: "Script", type: "code" }
{ id: "script", label: "Script", type: "code" },
];
const unpackSmartQuery = memoize(query => {
return map(query, e => {
const unpackSmartQuery = memoize((query) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("virtualParameters", e[2], e[3]);
return e;
@@ -64,7 +64,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store
.resourceExists("virtualParameters", id)
.then(exists => {
.then((exists) => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Virtual parameter already exists" });
@@ -96,7 +96,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
.catch((err) => {
store.fulfill(0, Date.now());
reject(err);
});
@@ -108,19 +108,21 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const formData = {
resource: "virtualParameters",
attributes: attributes
attributes: attributes,
};
const getDownloadUrl = memoize(filter => {
const getDownloadUrl = memoize((filter) => {
const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id;
return `api/virtualParameters.csv?${m.buildQueryString({
filter: stringify(filter),
columns: JSON.stringify(cols)
columns: JSON.stringify(cols),
})}`;
});
export function init(args): Promise<{}> {
export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("virtualParameters", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -141,7 +143,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => {
return {
view: vnode => {
view: (vnode) => {
document.title = "Virtual Parameters - GenieACS";
function showMore(): void {
@@ -184,7 +186,7 @@ export const component: ClosureComponent = (): Component => {
const virtualParameters = store.fetch("virtualParameters", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort
sort: sort,
});
const count = store.count("virtualParameters", filter);
@@ -199,7 +201,7 @@ export const component: ClosureComponent = (): Component => {
attrs["sortAttributes"] = sortAttributes;
attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl;
attrs["recordActionsCallback"] = virtualParameter => {
attrs["recordActionsCallback"] = (virtualParameter) => {
return [
m(
"a",
@@ -212,9 +214,9 @@ export const component: ClosureComponent = (): Component => {
{
base: virtualParameter,
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, false)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -226,27 +228,27 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"Show"
)
),
];
};
if (window.authorizer.hasAccess("virtualParameters", 3)) {
attrs["actionsCallback"] = (selected): Children => {
attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [
m(
"button.primary",
@@ -259,9 +261,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
return new Promise((resolve) => {
putActionHandler(action, object, true)
.then(errors => {
.then((errors) => {
const errorList = errors
? Object.values(errors)
: [];
@@ -273,19 +275,19 @@ export const component: ClosureComponent = (): Component => {
}
resolve();
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
resolve();
});
});
}
},
},
formData
)
);
};
overlay.open(cb);
}
},
},
"New"
),
@@ -294,7 +296,7 @@ export const component: ClosureComponent = (): Component => {
{
title: "Delete selected virtual parameters",
disabled: !selected.size,
onclick: e => {
onclick: (e) => {
if (
!confirm(
`Deleting ${selected.size} virtual parameters. Are you sure?`
@@ -305,25 +307,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false;
e.target.disabled = true;
Promise.all(
Array.from(selected).map(id =>
Array.from(selected).map((id) =>
store.deleteResource("virtualParameters", id)
)
)
.then(res => {
.then((res) => {
notifications.push(
"success",
`${res.length} virtual parameters deleted`
);
store.fulfill(0, Date.now());
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(0, Date.now());
});
}
},
},
"Delete"
)
),
];
};
}
@@ -336,8 +338,8 @@ export const component: ClosureComponent = (): Component => {
return [
m("h1", "Listing virtual parameters"),
m(filterComponent, filterAttrs),
m(indexTableComponent, attrs)
m(indexTableComponent, attrs),
];
}
},
};
};
+13 -13
View File
@@ -20,7 +20,7 @@
import m, { ClosureComponent, Component } from "mithril";
import * as notifications from "./notifications";
export async function init(): Promise<{}> {
export async function init(): Promise<Record<string, unknown>> {
return m.request({ url: "init" });
}
@@ -39,18 +39,18 @@ export const component: ClosureComponent = (vnode): Component => {
"filters",
"device",
"index",
"overview"
].map(s => {
"overview",
].map((s) => {
if (!options[s]) selected.delete(s);
return m("input", {
type: "checkbox",
checked: selected.has(s),
disabled: !options[s],
style: "display: inline; margin-right: 0.5em;",
onclick: e => {
onclick: (e) => {
if (e.target.checked) selected.add(s);
else selected.delete(s);
}
},
});
});
@@ -74,7 +74,7 @@ export const component: ClosureComponent = (vnode): Component => {
{
style: "margin: 10px;",
disabled: selected.size === 0,
onclick: e => {
onclick: (e) => {
e.target.disabled = true;
const opts = {};
@@ -83,18 +83,18 @@ export const component: ClosureComponent = (vnode): Component => {
m.request({
method: "POST",
url: "init",
body: opts
body: opts,
})
.then(() => {
setTimeout(() => {
m.request({ url: "init" }).then(o => {
m.request({ url: "init" }).then((o) => {
e.target.disabled = false;
options = o;
notifications.push("success", "Initialization complete", {
"Open Sesame!": () => {
m.route.set("/login");
window.location.reload();
}
},
});
});
}, 3000);
@@ -104,14 +104,14 @@ export const component: ClosureComponent = (vnode): Component => {
);
}
})
.catch(err => {
.catch((err) => {
notifications.push("error", err.message);
});
}
},
},
"ABRACADABRA!"
)
),
]);
}
},
};
};