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