Refactor components

Implement context mechanism to pass default attributes to descendant
components.
This commit is contained in:
Zaid Abdulla committed 2019-02-12 23:11:27 -08:00
1 parent d46d133878
commit ee8c43747e
35 files changed
+1903 -1831

No files matched your search

+58 -58
View File
@@ -2,63 +2,63 @@
import m from "mithril";
const menu = {
view: vnode => {
const active = { [vnode.attrs.page]: "active" };
const tabs = [];
export default function menu() {
return {
view: vnode => {
const active = { [vnode.attrs.page]: "active" };
const tabs = [];
if (window.authorizer.hasAccess("presets", 1)) {
tabs.push(
m(
"li",
{ class: active["presets"] },
m("a", { href: "#!/admin/presets" }, "Presets")
)
);
if (window.authorizer.hasAccess("presets", 1)) {
tabs.push(
m(
"li",
{ class: active["presets"] },
m("a", { href: "#!/admin/presets" }, "Presets")
)
);
}
if (window.authorizer.hasAccess("provisions", 1)) {
tabs.push(
m(
"li",
{ class: active["provisions"] },
m("a", { href: "#!/admin/provisions" }, "Provisions")
)
);
}
if (window.authorizer.hasAccess("virtualParameters", 1)) {
tabs.push(
m(
"li",
{ class: active["virtualParameters"] },
m("a", { href: "#!/admin/virtualParameters" }, "Virtual Parameters")
)
);
}
if (window.authorizer.hasAccess("files", 1)) {
tabs.push(
m(
"li",
{ class: active["files"] },
m("a", { href: "#!/admin/files" }, "Files")
)
);
}
if (window.authorizer.hasAccess("config", 1)) {
tabs.push(
m(
"li",
{ class: active["config"] },
m("a", { href: "#!/admin/config" }, "Config")
)
);
}
return m("nav#side-menu", m("ul", tabs));
}
if (window.authorizer.hasAccess("provisions", 1)) {
tabs.push(
m(
"li",
{ class: active["provisions"] },
m("a", { href: "#!/admin/provisions" }, "Provisions")
)
);
}
if (window.authorizer.hasAccess("virtualParameters", 1)) {
tabs.push(
m(
"li",
{ class: active["virtualParameters"] },
m("a", { href: "#!/admin/virtualParameters" }, "Virtual Parameters")
)
);
}
if (window.authorizer.hasAccess("files", 1)) {
tabs.push(
m(
"li",
{ class: active["files"] },
m("a", { href: "#!/admin/files" }, "Files")
)
);
}
if (window.authorizer.hasAccess("config", 1)) {
tabs.push(
m(
"li",
{ class: active["config"] },
m("a", { href: "#!/admin/config" }, "Config")
)
);
}
return m("nav#side-menu", m("ul", tabs));
}
};
export default menu;
};
}
+2 -1
View File
@@ -17,6 +17,7 @@ import * as filesPage from "./files-page";
import * as configPage from "./config-page";
import Authorizer from "../lib//common/authorizer";
import * as notifications from "./notifications";
import { contextifyComponent } from "./components";
window.authorizer = new Authorizer(window.permissionSets);
@@ -49,7 +50,7 @@ function pagify(pageName, page) {
lastRenderTimestamp = Date.now();
let p;
if (state && state.error) p = m(errorPage.component, state);
else p = m(page.component, state);
else p = m(contextifyComponent(page.component), state);
fulfill();
return m(layout, { page: pageName }, p);
}
+71 -4
View File
@@ -1,5 +1,6 @@
"use strict";
import m from "mithril";
import parameter from "./components/parameter";
import parameterList from "./components/parameter-list";
import parameterTable from "./components/parameter-table";
@@ -12,6 +13,7 @@ import deviceActions from "./components/device-actions";
import tags from "./components/tags";
import ping from "./components/ping";
import deviceLink from "./components/device-link";
import longTextComponent from "./long-text-component";
const comps = {
parameter,
@@ -25,11 +27,76 @@ const comps = {
"device-actions": deviceActions,
tags,
ping,
"device-link": deviceLink
"device-link": deviceLink,
"long-text": longTextComponent
};
export function get(name) {
const c = comps[name];
if (!c) throw new Error(`No such component '${name}'`);
const contextifiedComponents = new WeakMap();
const vnodeContext = new WeakMap();
function contextFn(context, ...argumentsList) {
const vnode = Reflect.apply(M, undefined, argumentsList);
vnodeContext.set(vnode, context);
return vnode;
}
const M = new Proxy(m, {
apply: (target, thisArg, argumentsList) => {
const c = argumentsList[0];
if (typeof c !== "string") argumentsList[0] = contextifyComponent(c);
else if (comps[c]) argumentsList[0] = contextifyComponent(comps[c]);
return Reflect.apply(target, undefined, argumentsList);
},
get: (target, prop) => {
if (prop === "context") return contextFn;
else return Reflect.get(target, prop);
}
});
function applyContext(vnode, parentContext) {
if (Array.isArray(vnode)) {
for (const c of vnode) applyContext(c, parentContext);
} else if (vnode && typeof vnode === "object" && vnode.tag) {
const vc = Object.assign({}, parentContext, vnodeContext.get(vnode));
vnodeContext.set(vnode, vc);
vnode.attrs = Object.assign({}, vc, vnode.attrs);
if (vnode.children && vnode.children.length)
for (const c of vnode.children) applyContext(c, vc);
}
}
export function contextifyComponent(component) {
let c = contextifiedComponents.get(component);
if (!c) {
if (typeof component !== "function") {
c = Object.assign({}, component);
const view = component.view;
c.view = vnode => {
const context = vnodeContext.get(vnode) || {};
const res = view(vnode, M);
applyContext(res, context);
return res;
};
} else if (!component.prototype.view) {
c = initialNode => {
const state = component(initialNode);
const view = state.view;
state.view = vnode => {
const context = vnodeContext.get(vnode) || {};
const res = view(vnode, M);
applyContext(res, context);
return res;
};
return state;
};
} else {
// TODO support class components
throw new Error("Class components not supported");
}
contextifiedComponents.set(component, c);
}
return c;
}
export { M as m };
+118 -120
View File
@@ -1,9 +1,7 @@
"use strict";
import m from "mithril";
import * as components from "../components";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import longTextComponent from "../long-text-component";
import { parse } from "../../lib/common/expression-parser";
import memoize from "../../lib/common/memoize";
@@ -13,125 +11,125 @@ function escapeRegExp(str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}
const component = {
view: vnode => {
const device = vnode.attrs.device;
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
const search = m("input", {
type: "text",
placeholder: "Search parameters",
oninput: e => {
vnode.state.searchString = e.target.value;
e.redraw = false;
clearTimeout(vnode.state.timeout);
vnode.state.timeout = setTimeout(m.redraw, 500);
}
});
const instanceRegex = /\.[0-9]+$/;
let re;
if (vnode.state.searchString) {
const keywords = vnode.state.searchString.split(" ").filter(s => s);
if (keywords.length)
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
}
const rows = Object.keys(device)
.sort()
.map(k => {
const p = device[k];
const val = [];
const attrs = {};
if (re) {
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
if (!re.test(str)) attrs.style = "display: none;";
const search = m("input", {
type: "text",
placeholder: "Search parameters",
oninput: e => {
vnode.state.searchString = e.target.value;
e.redraw = false;
clearTimeout(vnode.state.timeout);
vnode.state.timeout = setTimeout(m.redraw, 500);
}
if (p.object === false) {
val.push(
m(
components.get("parameter"),
Object.assign({ device: device, parameter: memoizedParse(k) })
)
);
} else if (p.object && p.writable) {
if (instanceRegex.test(k)) {
val.push(
m(
"button",
{
title: "Delete this instance",
onclick: () => {
taskQueue.queueTask({
name: "deleteObject",
device: device["DeviceID.ID"].value[0],
objectName: k
});
}
},
"✕"
)
);
} else {
val.push(
m(
"button",
{
title: "Create a new instance",
onclick: () => {
taskQueue.queueTask({
name: "addObject",
device: device["DeviceID.ID"].value[0],
objectName: k
});
}
},
"🞢"
)
);
}
}
val.push(
m(
"button",
{
title: "Refresh tree",
onclick: () => {
taskQueue.queueTask({
name: "getParameterValues",
device: device["DeviceID.ID"].value[0],
parameterNames: [k]
});
}
},
"↺"
)
);
return m(
"tr",
attrs,
m("td.left", m(longTextComponent, { text: k })),
m("td.right", val)
);
});
return m(
".all-parameters",
m(
"a.download-csv",
{
href: `/api/devices/${device["DeviceID.ID"].value[0]}.csv`,
download: "",
style: "float: right;"
},
"Download"
),
search,
m(".parameter-list", m("table", m("tbody", rows)))
);
}
};
const instanceRegex = /\.[0-9]+$/;
let re;
if (vnode.state.searchString) {
const keywords = vnode.state.searchString.split(" ").filter(s => s);
if (keywords.length)
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
}
export default component;
const rows = Object.keys(device)
.sort()
.map(k => {
const p = device[k];
const val = [];
const attrs = {};
if (re) {
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
if (!re.test(str)) attrs.style = "display: none;";
}
if (p.object === false) {
val.push(
m(
"parameter",
Object.assign({ device: device, parameter: memoizedParse(k) })
)
);
} else if (p.object && p.writable) {
if (instanceRegex.test(k)) {
val.push(
m(
"button",
{
title: "Delete this instance",
onclick: () => {
taskQueue.queueTask({
name: "deleteObject",
device: device["DeviceID.ID"].value[0],
objectName: k
});
}
},
"✕"
)
);
} else {
val.push(
m(
"button",
{
title: "Create a new instance",
onclick: () => {
taskQueue.queueTask({
name: "addObject",
device: device["DeviceID.ID"].value[0],
objectName: k
});
}
},
"🞢"
)
);
}
}
val.push(
m(
"button",
{
title: "Refresh tree",
onclick: () => {
taskQueue.queueTask({
name: "getParameterValues",
device: device["DeviceID.ID"].value[0],
parameterNames: [k]
});
}
},
"↺"
)
);
return m(
"tr",
attrs,
m("td.left", m("long-text", { text: k })),
m("td.right", val)
);
});
return m(
".all-parameters",
m(
"a.download-csv",
{
href: `/api/devices/${device["DeviceID.ID"].value[0]}.csv`,
download: "",
style: "float: right;"
},
"Download"
),
search,
m(".parameter-list", m("table", m("tbody", rows)))
);
}
};
}
+17 -19
View File
@@ -1,24 +1,22 @@
"use strict";
import m from "mithril";
import * as components from "../components";
import { m } from "../components";
import * as store from "../store";
const component = {
view: vnode => {
if (vnode.attrs.filter) {
if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
return null;
export default function component() {
return {
view: vnode => {
if (vnode.attrs.filter) {
if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
return null;
}
const children = Object.values(vnode.attrs.components).map(c => {
if (typeof c !== "object") return `${c}`;
return m(c.type, c);
});
if (vnode.attrs.element) return m(vnode.attrs.element, children);
else return children;
}
const children = Object.values(vnode.attrs.components).map(c => {
if (typeof c !== "object") return `${c}`;
const attrs = Object.assign({}, vnode.attrs, c);
return m(components.get(attrs.type), attrs);
});
if (vnode.attrs.element) return m(vnode.attrs.element, children);
else return children;
}
};
export default component;
};
}
+79 -79
View File
@@ -1,90 +1,90 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as notifications from "../notifications";
import * as store from "../store";
const component = {
view: vnode => {
const device = vnode.attrs.device;
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
const buttons = [];
const buttons = [];
buttons.push(
m(
"button.primary",
{
title: "Reboot device",
onclick: () => {
taskQueue.queueTask({
name: "reboot",
device: device["DeviceID.ID"].value[0]
});
}
},
"Reboot"
)
);
buttons.push(
m(
"button.critical",
{
title: "Factory reset device",
onclick: () => {
taskQueue.queueTask({
name: "factoryReset",
device: device["DeviceID.ID"].value[0]
});
}
},
"Reset"
)
);
buttons.push(
m(
"button.critical",
{
title: "Push a firmware or a config file",
onclick: () => {
taskQueue.stageDownload({
name: "download",
device: device["DeviceID.ID"].value[0]
});
}
},
"Push file"
)
);
buttons.push(
m(
"button.primary",
{
title: "Delete device",
onclick: () => {
if (!confirm("Deleting this device. Are you sure?")) return;
const deviceId = device["DeviceID.ID"].value[0];
store
.deleteResource("devices", deviceId)
.then(() => {
notifications.push("success", `${deviceId}: Device deleted`);
m.route.set("/devices");
})
.catch(err => {
notifications.push("error", err.message);
buttons.push(
m(
"button.primary",
{
title: "Reboot device",
onclick: () => {
taskQueue.queueTask({
name: "reboot",
device: device["DeviceID.ID"].value[0]
});
}
},
"Delete"
)
);
}
},
"Reboot"
)
);
return m(".actions-bar", buttons);
}
};
buttons.push(
m(
"button.critical",
{
title: "Factory reset device",
onclick: () => {
taskQueue.queueTask({
name: "factoryReset",
device: device["DeviceID.ID"].value[0]
});
}
},
"Reset"
)
);
export default component;
buttons.push(
m(
"button.critical",
{
title: "Push a firmware or a config file",
onclick: () => {
taskQueue.stageDownload({
name: "download",
device: device["DeviceID.ID"].value[0]
});
}
},
"Push file"
)
);
buttons.push(
m(
"button.primary",
{
title: "Delete device",
onclick: () => {
if (!confirm("Deleting this device. Are you sure?")) return;
const deviceId = device["DeviceID.ID"].value[0];
store
.deleteResource("devices", deviceId)
.then(() => {
notifications.push("success", `${deviceId}: Device deleted`);
m.route.set("/devices");
})
.catch(err => {
notifications.push("error", err.message);
});
}
},
"Delete"
)
);
return m(".actions-bar", buttons);
}
};
}
+68 -64
View File
@@ -1,71 +1,75 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as store from "../store";
import * as notifications from "../notifications";
const component = {
view: vnode => {
const device = vnode.attrs.device;
const deviceId = device["DeviceID.ID"].value[0];
const faults = store.fetch("faults", [
"AND",
[">", ["PARAM", "_id"], `${deviceId}:`],
["<", ["PARAM", "_id"], `${deviceId}:zzzz`]
]);
const headers = ["Channel", "Code", "Message", "Retries", "Timestamp"].map(
l => m("th", l)
);
const thead = m("thead", m("tr", headers));
const rows = [];
for (const f of faults.value) {
rows.push([
m("td", f["channel"]),
m("td", f["code"]),
m("td", f["message"]),
m("td", f["retries"]),
m("td", new Date(f["timestamp"]).toLocaleString()),
m(
"td",
m(
"button",
{
title: "Delete fault",
onclick: e => {
e.redraw = false;
store
.deleteResource("faults", f["_id"])
.then(() => {
notifications.push("success", "Fault deleted");
store.fulfill(Date.now(), Date.now());
m.redraw();
})
.catch(err => {
notifications.push("error", err.message);
store.fulfill(Date.now(), Date.now());
});
}
},
"✕"
)
)
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
const deviceId = device["DeviceID.ID"].value[0];
const faults = store.fetch("faults", [
"AND",
[">", ["PARAM", "_id"], `${deviceId}:`],
["<", ["PARAM", "_id"], `${deviceId}:zzzz`]
]);
const headers = [
"Channel",
"Code",
"Message",
"Retries",
"Timestamp"
].map(l => m("th", l));
const thead = m("thead", m("tr", headers));
const rows = [];
for (const f of faults.value) {
rows.push([
m("td", f["channel"]),
m("td", f["code"]),
m("td", f["message"]),
m("td", f["retries"]),
m("td", new Date(f["timestamp"]).toLocaleString()),
m(
"td",
m(
"button",
{
title: "Delete fault",
onclick: e => {
e.redraw = false;
store
.deleteResource("faults", f["_id"])
.then(() => {
notifications.push("success", "Fault deleted");
store.fulfill(Date.now(), Date.now());
m.redraw();
})
.catch(err => {
notifications.push("error", err.message);
store.fulfill(Date.now(), Date.now());
});
}
},
"✕"
)
)
]);
}
let tbody;
if (rows.length) {
tbody = m("tbody", rows.map(r => m("tr", r)));
} else {
tbody = m(
"tbody",
m("tr.empty", m("td", { colspan: headers.length }, "No faults"))
);
}
return m("table.table", thead, tbody);
}
let tbody;
if (rows.length) {
tbody = m("tbody", rows.map(r => m("tr", r)));
} else {
tbody = m(
"tbody",
m("tr.empty", m("td", { colspan: headers.length }, "No faults"))
);
}
return m("table.table", thead, tbody);
}
};
export default component;
};
}
+23 -24
View File
@@ -1,29 +1,28 @@
"use strict";
import m from "mithril";
import * as components from "../components";
import { m } from "../components";
const component = {
view: vnode => {
let deviceId;
if (vnode.attrs.device)
deviceId = vnode.attrs.device["DeviceID.ID"].value[0];
export default function component() {
return {
view: vnode => {
let deviceId;
if (vnode.attrs.device)
deviceId = vnode.attrs.device["DeviceID.ID"].value[0];
const children = Object.values(vnode.attrs.components).map(c => {
if (typeof c !== "object") return `${c}`;
const attrs = Object.assign({}, vnode.attrs, c);
return m(components.get(attrs.type), attrs);
});
if (deviceId) {
return m(
"a",
{ href: `#!/devices/${encodeURIComponent(deviceId)}` },
children
);
} else {
return children;
const children = Object.values(vnode.attrs.components).map(c => {
if (typeof c !== "object") return `${c}`;
const attrs = Object.assign({}, vnode.attrs, c);
return m(attrs.type, attrs);
});
if (deviceId) {
return m(
"a",
{ href: `#!/devices/${encodeURIComponent(deviceId)}` },
children
);
} else {
return children;
}
}
}
};
export default component;
};
}
+29 -29
View File
@@ -1,38 +1,38 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import config from "../config";
import * as store from "../store";
const CHARTS = config.ui.overview.charts;
const component = {
view: vnode => {
const device = vnode.attrs.device;
const chart = CHARTS[vnode.attrs.chart];
for (const slice of Object.values(chart.slices)) {
const filter = slice.filter;
if (store.evaluateExpression(filter, device)) {
const dot = m(
"svg",
{
width: "1em",
height: "1em",
xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
},
m("circle", {
cx: "0.5em",
cy: "0.5em",
r: "0.4em",
fill: slice.color
})
);
return m("span.overview-dot", dot, `${slice.label}`);
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
const chart = CHARTS[vnode.attrs.chart];
for (const slice of Object.values(chart.slices)) {
const filter = slice.filter;
if (store.evaluateExpression(filter, device)) {
const dot = m(
"svg",
{
width: "1em",
height: "1em",
xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
},
m("circle", {
cx: "0.5em",
cy: "0.5em",
r: "0.4em",
fill: slice.color
})
);
return m("span.overview-dot", dot, `${slice.label}`);
}
}
return null;
}
return null;
}
};
export default component;
};
}
+29 -29
View File
@@ -1,35 +1,35 @@
"use strict";
import m from "mithril";
import { get } from "../components";
import memoize from "../../lib/common/memoize";
import { m } from "../components";
const getChildAttrs = memoize((attrs, device) =>
Object.assign({}, attrs, { device: device })
);
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
export default {
view: vnode => {
const device = vnode.attrs.device;
const rows = Object.values(vnode.attrs.parameters).map(parameter => {
const p = m.context(
{
device: device,
parameter: parameter.parameter
},
parameter.type || "parameter",
parameter
);
const rows = Object.values(vnode.attrs.parameters).map(parameter => {
const p = m(
get(parameter.type || "parameter"),
getChildAttrs(parameter, device)
);
return m(
"tr",
{
onupdate: vn => {
vn.dom.style.display = p.dom ? "" : "none";
}
},
m("th", parameter.label),
m("td", p)
);
});
return m(
"tr",
{
onupdate: vn => {
vn.dom.style.display = p.dom ? "" : "none";
}
},
m("th", parameter.label),
m("td", p)
);
});
return m("table.parameter-list", rows);
}
};
return m("table.parameter-list", rows);
}
};
}
+108 -106
View File
@@ -1,132 +1,134 @@
"use strict";
import m from "mithril";
import * as components from "../components";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as store from "../store";
import * as expressionParser from "../../lib/common/expression-parser";
const component = {
oninit: vnode => {
const obj = vnode.attrs.parameter;
if (!Array.isArray(obj) || !obj[0] === "PARAM")
throw new Error("Object must be a parameter path");
vnode.state.object = obj[1];
vnode.state.parameters = Object.values(vnode.attrs.childParameters);
},
view: vnode => {
const device = vnode.attrs.device;
const object = store.evaluateExpression(vnode.state.object, device);
const parameters = vnode.state.parameters;
export default function component() {
return {
oninit: vnode => {
const obj = vnode.attrs.parameter;
if (!Array.isArray(obj) || !obj[0] === "PARAM")
throw new Error("Object must be a parameter path");
vnode.state.object = obj[1];
vnode.state.parameters = Object.values(vnode.attrs.childParameters);
},
view: vnode => {
const device = vnode.attrs.device;
const object = store.evaluateExpression(vnode.state.object, device);
const parameters = vnode.state.parameters;
if (!device[object]) return null;
if (!device[object]) return null;
const instances = new Set();
const prefix = `${object}.`;
for (const p in device) {
if (p.startsWith(prefix)) {
const i = p.indexOf(".", prefix.length);
if (i === -1) instances.add(p);
else instances.add(p.slice(0, i));
const instances = new Set();
const prefix = `${object}.`;
for (const p in device) {
if (p.startsWith(prefix)) {
const i = p.indexOf(".", prefix.length);
if (i === -1) instances.add(p);
else instances.add(p.slice(0, i));
}
}
}
const headers = Object.values(parameters).map(p => m("th", p.label));
const headers = Object.values(parameters).map(p => m("th", p.label));
const thead = m("thead", m("tr", headers));
const thead = m("thead", m("tr", headers));
const rows = [];
for (const i of instances) {
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
const rows = [];
for (const i of instances) {
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
filter = expressionParser.map(filter, e => {
if (Array.isArray(e) && e[0] === "PARAM")
return ["PARAM", ["||", i, ".", e[1]]];
return e;
});
if (!store.evaluateExpression(filter, device)) continue;
const row = parameters.map(p => {
const param = expressionParser.map(p.parameter, e => {
filter = expressionParser.map(filter, e => {
if (Array.isArray(e) && e[0] === "PARAM")
return ["PARAM", ["||", i, ".", e[1]]];
return e;
});
return m(
"td",
m(
components.get(p.type || "parameter"),
Object.assign({}, p, {
device: device,
parameter: param,
label: ""
})
)
);
});
if (device[i].writable === true) {
row.push(
m(
if (!store.evaluateExpression(filter, device)) continue;
const row = parameters.map(p => {
const param = expressionParser.map(p.parameter, e => {
if (Array.isArray(e) && e[0] === "PARAM")
return ["PARAM", ["||", i, ".", e[1]]];
return e;
});
return m(
"td",
m(
"button",
m.context(
{
title: "Delete this instance",
onclick: () => {
taskQueue.queueTask({
name: "deleteObject",
device: device["DeviceID.ID"].value[0],
objectName: i
});
}
device: device,
parameter: param
},
"✕"
p.type || "parameter",
Object.assign({}, p, {
device: device,
parameter: param
})
)
);
});
if (device[i].writable === true) {
row.push(
m(
"td",
m(
"button",
{
title: "Delete this instance",
onclick: () => {
taskQueue.queueTask({
name: "deleteObject",
device: device["DeviceID.ID"].value[0],
objectName: i
});
}
},
"✕"
)
)
);
}
rows.push(m("tr", row));
}
if (!rows.length) {
rows.push(
m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
);
}
if (device[object].writable === true) {
rows.push(
m(
"tr",
m("td", { colspan: headers.length }),
m(
"td",
m(
"button",
{
title: "Create a new instance",
onclick: () => {
taskQueue.queueTask({
name: "addObject",
device: device["DeviceID.ID"].value[0],
objectName: object
});
}
},
"🞢"
)
)
)
);
}
rows.push(m("tr", row));
let label;
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
return [label, m("table.table", thead, m("tbody", rows))];
}
if (!rows.length) {
rows.push(
m("tr.empty", m("td", { colspan: headers.length }, "No instances"))
);
}
if (device[object].writable === true) {
rows.push(
m(
"tr",
m("td", { colspan: headers.length }),
m(
"td",
m(
"button",
{
title: "Create a new instance",
onclick: () => {
taskQueue.queueTask({
name: "addObject",
device: device["DeviceID.ID"].value[0],
objectName: object
});
}
},
"🞢"
)
)
)
);
}
let label;
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
return [label, m("table.table", thead, m("tbody", rows))];
}
};
export default component;
};
}
+52 -53
View File
@@ -1,12 +1,11 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as store from "../store";
import * as expression from "../../lib/common/expression";
import memoize from "../../lib/common/memoize";
import timeAgo from "../timeago";
import longTextComponent from "../long-text-component";
const evaluateParam = memoize((exp, obj, now) => {
let timestamp = now;
@@ -32,62 +31,62 @@ const evaluateParam = memoize((exp, obj, now) => {
return { value, timestamp, parameter };
});
const component = {
view: vnode => {
const device = vnode.attrs.device;
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
const { value, timestamp, parameter } = evaluateParam(
vnode.attrs.parameter,
vnode.attrs.device,
store.getTimestamp()
);
const { value, timestamp, parameter } = evaluateParam(
vnode.attrs.parameter,
vnode.attrs.device,
store.getTimestamp()
);
if (value == null) return null;
if (value == null) return null;
let edit;
if (device[parameter] && device[parameter].writable) {
edit = m(
"button",
{
title: "Edit parameter value",
onclick: () => {
taskQueue.stageSpv({
name: "setParameterValues",
device: device["DeviceID.ID"].value[0],
parameterValues: [
[
parameter,
device[parameter].value[0],
device[parameter].value[1]
let edit;
if (device[parameter] && device[parameter].writable) {
edit = m(
"button",
{
title: "Edit parameter value",
onclick: () => {
taskQueue.stageSpv({
name: "setParameterValues",
device: device["DeviceID.ID"].value[0],
parameterValues: [
[
parameter,
device[parameter].value[0],
device[parameter].value[1]
]
]
]
});
});
}
},
"✎"
);
}
const el = m("long-text", { text: `${value}` });
return m(
"span",
{
class: "parameter-value",
onmouseover: e => {
e.redraw = false;
// Don't update any child element
if (e.target === el.dom) {
const now = Date.now();
const localeString = new Date(timestamp).toLocaleString();
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
}
}
},
"✎"
el,
edit
);
}
const el = m(longTextComponent, { text: `${value}` });
return m(
"span",
{
class: "parameter-value",
onmouseover: e => {
e.redraw = false;
// Don't update any child element
if (e.target === el.dom) {
const now = Date.now();
const localeString = new Date(timestamp).toLocaleString();
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
}
}
},
el,
edit
);
}
};
export default component;
};
}
+45 -45
View File
@@ -1,56 +1,56 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as store from "../store";
const REFRESH_INTERVAL = 3000;
const component = {
oninit: vnode => {
vnode.state.timestamp = 0;
vnode.state.ping = null;
vnode.state.host = null;
vnode.state.timeout = null;
},
view: vnode => {
const refresh = () => {
const device = vnode.attrs.device;
let param =
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
if (!param)
param = device["Device.ManagementServer.ConnectionRequestURL"];
if (!param || !param.value) return;
export default function component() {
return {
oninit: vnode => {
vnode.state.timestamp = 0;
vnode.state.ping = null;
vnode.state.host = null;
vnode.state.timeout = null;
},
view: vnode => {
const refresh = () => {
const device = vnode.attrs.device;
let param =
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
if (!param)
param = device["Device.ManagementServer.ConnectionRequestURL"];
if (!param || !param.value) return;
const url = new URL(param.value[0]);
vnode.state.host = url.hostname;
vnode.state.timestamp = Date.now();
store
.ping(vnode.state.host)
.then(res => {
vnode.state.ping = res;
})
.catch(() => {
// Do nothing
});
};
const url = new URL(param.value[0]);
vnode.state.host = url.hostname;
vnode.state.timestamp = Date.now();
store
.ping(vnode.state.host)
.then(res => {
vnode.state.ping = res;
})
.catch(() => {
// Do nothing
});
};
const t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now();
clearTimeout(vnode.state.timeout);
if (t <= 0) refresh();
else vnode.state.timeout = setTimeout(refresh, t);
const t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now();
clearTimeout(vnode.state.timeout);
if (t <= 0) refresh();
else vnode.state.timeout = setTimeout(refresh, t);
if (vnode.state.host) {
let status = "";
if (vnode.state.ping) {
if (vnode.state.ping.avg != null)
status = `${Math.trunc(vnode.state.ping.avg)} ms`;
else status = "Unreachable";
if (vnode.state.host) {
let status = "";
if (vnode.state.ping) {
if (vnode.state.ping.avg != null)
status = `${Math.trunc(vnode.state.ping.avg)} ms`;
else status = "Unreachable";
}
return m("div", `Pinging ${vnode.state.host}: ${status}`);
}
return m("div", `Pinging ${vnode.state.host}: ${status}`);
return null;
}
return null;
}
};
export default component;
};
}
+61 -61
View File
@@ -1,71 +1,71 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as store from "../store";
import * as notifications from "../notifications";
const component = {
view: vnode => {
const device = vnode.attrs.device;
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
return m(
"button.primary",
{
title: "Initiate session and refresh basic parameters",
onclick: e => {
e.target.disabled = true;
const params = Object.values(vnode.attrs.parameters)
.map(exp => {
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
return store.evaluateExpression(exp[1], device);
})
.filter(exp => !!exp);
return m(
"button.primary",
{
title: "Initiate session and refresh basic parameters",
onclick: e => {
e.target.disabled = true;
const params = Object.values(vnode.attrs.parameters)
.map(exp => {
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
return store.evaluateExpression(exp[1], device);
})
.filter(exp => !!exp);
const task = {
name: "getParameterValues",
parameterNames: params,
device: device["DeviceID.ID"].value[0]
};
const task = {
name: "getParameterValues",
parameterNames: params,
device: device["DeviceID.ID"].value[0]
};
taskQueue
.commit(
[task],
(deviceId, err, connectionRequestStatus, tasks2) => {
if (err) {
notifications.push("error", `${deviceId}: ${err.message}`);
return;
taskQueue
.commit(
[task],
(deviceId, err, connectionRequestStatus, tasks2) => {
if (err) {
notifications.push("error", `${deviceId}: ${err.message}`);
return;
}
for (const t of tasks2)
if (t.status === "stale") taskQueue.deleteTask(t);
if (connectionRequestStatus !== "OK") {
notifications.push(
"error",
`${deviceId}: ${connectionRequestStatus}`
);
} else if (tasks2[0].status === "stale") {
notifications.push(
"error",
`${deviceId}: No contact from device`
);
} else if (tasks2[0].status === "fault") {
notifications.push("error", `${deviceId}: Refresh faulted`);
} else {
notifications.push("success", `${deviceId}: Summoned`);
}
}
for (const t of tasks2)
if (t.status === "stale") taskQueue.deleteTask(t);
if (connectionRequestStatus !== "OK") {
notifications.push(
"error",
`${deviceId}: ${connectionRequestStatus}`
);
} else if (tasks2[0].status === "stale") {
notifications.push(
"error",
`${deviceId}: No contact from device`
);
} else if (tasks2[0].status === "fault") {
notifications.push("error", `${deviceId}: Refresh faulted`);
} else {
notifications.push("success", `${deviceId}: Summoned`);
}
}
)
.then(() => {
e.target.disabled = false;
store.fulfill(0, Date.now());
});
}
},
"Summon"
);
}
};
export default component;
)
.then(() => {
e.target.disabled = false;
store.fulfill(0, Date.now());
});
}
},
"Summon"
);
}
};
}
+60 -54
View File
@@ -1,37 +1,75 @@
"use strict";
import m from "mithril";
import { m } from "../components";
import * as notifications from "../notifications";
import * as store from "../store";
const component = {
view: vnode => {
const device = vnode.attrs.device;
let writable = true;
if (vnode.attrs.writable != null) writable = vnode.attrs.writable;
export default function component() {
return {
view: vnode => {
const device = vnode.attrs.device;
let writable = true;
if (vnode.attrs.writable != null) writable = vnode.attrs.writable;
const tags = [];
for (const p of Object.keys(device))
if (p.startsWith("Tags.")) tags.push(p.slice(5));
const tags = [];
for (const p of Object.keys(device))
if (p.startsWith("Tags.")) tags.push(p.slice(5));
tags.sort();
tags.sort();
if (!writable) return m(".tags", tags.map(t => m("span.tag", t)));
if (!writable) return m(".tags", tags.map(t => m("span.tag", t)));
return m(
".tags",
tags.map(tag =>
return m(
".tags",
tags.map(tag =>
m(
"span.tag",
tag,
m(
"button",
{
onclick: e => {
e.target.disabled = true;
const deviceId = device["DeviceID.ID"].value[0];
store
.updateTags(deviceId, { [tag]: false })
.then(() => {
e.target.disabled = false;
notifications.push(
"success",
`${deviceId}: Tags updated`
);
store.fulfill(0, Date.now());
})
.catch(err => {
e.target.disabled = false;
notifications.push(
"error",
`${deviceId}: ${err.message}`
);
});
}
},
"✕"
)
)
),
m(
"span.tag",
tag,
"span.tag.writable",
m(
"button",
{
onclick: e => {
e.target.disabled = true;
const deviceId = device["DeviceID.ID"].value[0];
const tag = prompt(`Enter tag to assign to device:`);
if (!tag) {
e.target.disabled = false;
return;
}
store
.updateTags(deviceId, { [tag]: false })
.updateTags(deviceId, { [tag]: true })
.then(() => {
e.target.disabled = false;
notifications.push("success", `${deviceId}: Tags updated`);
@@ -43,42 +81,10 @@ const component = {
});
}
},
"✕"
"🞢"
)
)
),
m(
"span.tag.writable",
m(
"button",
{
onclick: e => {
e.target.disabled = true;
const deviceId = device["DeviceID.ID"].value[0];
const tag = prompt(`Enter tag to assign to device:`);
if (!tag) {
e.target.disabled = false;
return;
}
store
.updateTags(deviceId, { [tag]: true })
.then(() => {
e.target.disabled = false;
notifications.push("success", `${deviceId}: Tags updated`);
store.fulfill(0, Date.now());
})
.catch(err => {
e.target.disabled = false;
notifications.push("error", `${deviceId}: ${err.message}`);
});
}
},
"🞢"
)
)
);
}
};
export default component;
);
}
};
}
+76 -82
View File
@@ -1,12 +1,10 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import * as store from "./store";
import * as notifications from "./notifications";
import putFormComponent from "./put-form-component";
import * as overlay from "./overlay";
import longTextComponent from "./long-text-component";
import * as expressionParser from "../lib/common/expression-parser";
import { loadCodeMirror } from "./dynamic-loader";
@@ -84,7 +82,7 @@ function escapeRegExp(str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}
function init() {
export function init() {
if (!window.authorizer.hasAccess("config", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -177,14 +175,10 @@ function renderTable(confsResponse, searchString) {
m(
"tr",
attrs,
m("td.left", m(longTextComponent, { text: conf._id })),
m("td.left", m("long-text", { text: conf._id })),
m(
"td.right",
m("span", [
m(longTextComponent, { text: `${conf.value}` }),
edit,
del
])
m("span", [m("long-text", { text: `${conf.value}` }), edit, del])
)
)
);
@@ -196,80 +190,80 @@ function renderTable(confsResponse, searchString) {
return m("table", m("tbody", rows));
}
const component = {
view: vnode => {
document.title = "Config - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Config - GenieACS";
const search = m("input", {
type: "text",
placeholder: "Search config",
oninput: e => {
vnode.state.searchString = e.target.value;
e.redraw = false;
clearTimeout(vnode.state.timeout);
vnode.state.timeout = setTimeout(m.redraw, 250);
const search = m("input", {
type: "text",
placeholder: "Search config",
oninput: e => {
vnode.state.searchString = e.target.value;
e.redraw = false;
clearTimeout(vnode.state.timeout);
vnode.state.timeout = setTimeout(m.redraw, 250);
}
});
let newConfig;
if (window.authorizer.hasAccess("config", 3)) {
newConfig = m(
"button.primary",
{
title: "Create new config",
onclick: () => {
const cb = () => {
return m(
putFormComponent,
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, true)
.then(errors => {
errors = errors ? Object.values(errors) : [];
if (errors.length) {
for (const err of errors)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
}
},
formData
)
);
};
overlay.open(cb);
}
},
"New"
);
}
});
let newConfig;
if (window.authorizer.hasAccess("config", 3)) {
newConfig = m(
"button.primary",
{
title: "Create new config",
onclick: () => {
const cb = () => {
return m(
putFormComponent,
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, true)
.then(errors => {
errors = errors ? Object.values(errors) : [];
if (errors.length) {
for (const err of errors)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
}
},
formData
)
);
};
overlay.open(cb);
}
},
"New"
);
}
const confs = store.fetch("config", true);
const confs = store.fetch("config", true);
return [
m("h1", "Listing config"),
m(
".all-parameters",
search,
return [
m("h1", "Listing config"),
m(
".parameter-list",
{ style: "height: 400px" },
renderTable(confs, vnode.state.searchString)
),
m(".actions-bar", newConfig)
)
];
}
};
export { init, component };
".all-parameters",
search,
m(
".parameter-list",
{ style: "height: 400px" },
renderTable(confs, vnode.state.searchString)
),
m(".actions-bar", newConfig)
)
];
}
};
}
+16 -25
View File
@@ -1,12 +1,10 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import * as store from "./store";
import * as components from "./components";
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("devices", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -19,27 +17,20 @@ function init(args) {
});
}
const component = {
view: vnode => {
document.title = `${vnode.attrs.deviceId} - Devices - GenieACS`;
export function component() {
return {
view: vnode => {
document.title = `${vnode.attrs.deviceId} - Devices - GenieACS`;
const dev = store.fetch("devices", vnode.attrs.deviceFilter).value;
if (!dev.length) return "Loading";
const conf = config.ui.device;
const cmps = [];
const dev = store.fetch("devices", vnode.attrs.deviceFilter).value;
if (!dev.length) return "Loading";
const conf = config.ui.device;
const cmps = [];
for (const c of Object.values(conf)) {
cmps.push(
m(components.get(c["type"]), Object.assign({ device: dev[0] }, c))
);
for (const c of Object.values(conf))
cmps.push(m.context({ device: dev[0] }, c["type"], c));
return [m("h1", vnode.attrs.deviceId), cmps];
}
return [
m("h1", vnode.attrs.deviceId),
m("span", dev[0]["InternetGatewayDevice.DeviceInfo.SerialNumber"]),
cmps
];
}
};
export { init, component };
};
}
+75 -75
View File
@@ -1,11 +1,9 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as store from "./store";
import * as components from "./components";
import * as taskQueue from "./task-queue";
import * as notifications from "./notifications";
import { evaluate, extractParams } from "../lib/common/expression";
@@ -36,10 +34,6 @@ const getDownloadUrl = memoize((filter, indexParameters) => {
})}`;
});
const getChildAttrs = memoize((attrs, device) =>
Object.assign({}, attrs, { device: device })
);
const unpackSmartQuery = memoize(query => {
return expressionParser.map(query, e => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
@@ -48,7 +42,7 @@ const unpackSmartQuery = memoize(query => {
});
});
function init(args) {
export function init(args) {
return new Promise((resolve, reject) => {
if (!window.authorizer.hasAccess("devices", 2))
return void reject(new Error("You are not authorized to view this page"));
@@ -138,8 +132,11 @@ function renderTable(
},
m("td", checkbox),
parameters.map(p => {
const attrs = getChildAttrs(p, device);
const comp = m(components.get(attrs.type || "parameter"), attrs);
const comp = m.context(
{ device: device, parameter: p.parameter },
p.type || "parameter",
p
);
return m("td", comp);
}),
m(
@@ -339,71 +336,74 @@ function renderActions(selected) {
return m(".actions-bar", buttons);
}
const component = {
view: vnode => {
document.title = "Devices - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Devices - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/devices", ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/devices", ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const devs = store.fetch("devices", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("devices", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const d of devs.value) {
if (vnode.state.selected.has(d["DeviceID.ID"].value[0]))
selected.add(d["DeviceID.ID"].value[0]);
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/devices", ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/devices", ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const devs = store.fetch("devices", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("devices", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const d of devs.value) {
if (vnode.state.selected.has(d["DeviceID.ID"].value[0]))
selected.add(d["DeviceID.ID"].value[0]);
}
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(
vnode.attrs.filter,
vnode.attrs.indexParameters
);
return [
m("h1", "Listing devices"),
m(filterComponent, {
resource: "devices",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
devs,
vnode.attrs.indexParameters,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
),
renderActions(selected)
];
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(
vnode.attrs.filter,
vnode.attrs.indexParameters
);
return [
m("h1", "Listing devices"),
m(filterComponent, {
resource: "devices",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
devs,
vnode.attrs.indexParameters,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
),
renderActions(selected)
];
}
};
export { init, component };
};
}
+164 -163
View File
@@ -312,187 +312,188 @@ function renderNotifications(notifs) {
return notificationElements;
}
const component = {
view: vnode => {
const queue = taskQueue.getQueue();
const staging = taskQueue.getStaging();
const notifs = notifications.getNotifications();
export default function component() {
return {
view: vnode => {
const queue = taskQueue.getQueue();
const staging = taskQueue.getStaging();
const notifs = notifications.getNotifications();
let drawerElement, statusElement;
const notificationElements = renderNotifications(notifs);
const stagingElements = renderStaging(staging);
const queueElements = renderQueue(queue);
let drawerElement, statusElement;
const notificationElements = renderNotifications(notifs);
const stagingElements = renderStaging(staging);
const queueElements = renderQueue(queue);
function repositionNotifications() {
let top = 10;
for (const c of notificationElements) {
c.dom.style.top = top;
top += c.dom.offsetHeight + 10;
function repositionNotifications() {
let top = 10;
for (const c of notificationElements) {
c.dom.style.top = top;
top += c.dom.offsetHeight + 10;
}
}
}
function resizeDrawer() {
let height = statusElement.dom.offsetTop + statusElement.dom.offsetHeight;
if (stagingElements.length) {
for (const s of stagingElements)
height = Math.max(height, s.dom.offsetTop + s.dom.offsetHeight);
} else if (vnode.state.mouseIn) {
for (const c of drawerElement.children)
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
function resizeDrawer() {
let height =
statusElement.dom.offsetTop + statusElement.dom.offsetHeight;
if (stagingElements.length) {
for (const s of stagingElements)
height = Math.max(height, s.dom.offsetTop + s.dom.offsetHeight);
} else if (vnode.state.mouseIn) {
for (const c of drawerElement.children)
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
}
drawerElement.dom.style.height = height;
}
drawerElement.dom.style.height = height;
}
if (stagingElements.length + queueElements.length) {
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
for (const t of queue) statusCount[t.status] += 1;
if (stagingElements.length + queueElements.length) {
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
for (const t of queue) statusCount[t.status] += 1;
const actions = m(
".actions",
m(
"button.primary",
{
title: "Commit queued tasks",
disabled: !statusCount.queued,
onclick: () => {
const tasks = Array.from(taskQueue.getQueue()).filter(
t => t.status === "queued"
);
taskQueue
.commit(
tasks,
(deviceId, err, connectionRequestStatus, tasks2) => {
if (err) {
notifications.push(
"error",
`${deviceId}: ${err.message}`
);
return;
}
if (connectionRequestStatus !== "OK") {
notifications.push(
"error",
`${deviceId}: ${connectionRequestStatus}`
);
return;
}
for (const t of tasks2) {
if (t.status === "stale") {
const actions = m(
".actions",
m(
"button.primary",
{
title: "Commit queued tasks",
disabled: !statusCount.queued,
onclick: () => {
const tasks = Array.from(taskQueue.getQueue()).filter(
t => t.status === "queued"
);
taskQueue
.commit(
tasks,
(deviceId, err, connectionRequestStatus, tasks2) => {
if (err) {
notifications.push(
"error",
`${deviceId}: No contact from device`
);
return;
} else if (t.status === "fault") {
notifications.push(
"error",
`${deviceId}: Task(s) faulted`
`${deviceId}: ${err.message}`
);
return;
}
}
notifications.push(
"success",
`${deviceId}: Task(s) committed`
);
}
)
.then(() => {
store.fulfill(0, Date.now());
});
if (connectionRequestStatus !== "OK") {
notifications.push(
"error",
`${deviceId}: ${connectionRequestStatus}`
);
return;
}
for (const t of tasks2) {
if (t.status === "stale") {
notifications.push(
"error",
`${deviceId}: No contact from device`
);
return;
} else if (t.status === "fault") {
notifications.push(
"error",
`${deviceId}: Task(s) faulted`
);
return;
}
}
notifications.push(
"success",
`${deviceId}: Task(s) committed`
);
}
)
.then(() => {
store.fulfill(0, Date.now());
});
}
},
"Commit"
),
m(
"button",
{
title: "Clear tasks",
onclick: taskQueue.clear,
disabled: !queueElements.length
},
"Clear"
)
);
statusElement = m(
".status",
m(
"span.queued",
{ class: statusCount.queued ? "active" : "" },
`Queued: ${statusCount.queued}`
),
m(
"span.pending",
{ class: statusCount.pending ? "active" : "" },
`Pending: ${statusCount.pending}`
),
m(
"span.fault",
{ class: statusCount.fault ? "active" : "" },
`Fault: ${statusCount.fault}`
),
m(
"span.stale",
{ class: statusCount.stale ? "active" : "" },
`Stale: ${statusCount.stale}`
),
actions
);
drawerElement = m(
".drawer",
{
key: "drawer",
style: "opacity: 0;height: 0;",
oncreate: vnode2 => {
vnode.state.mouseIn = false;
vnode2.dom.style.opacity = 1;
resizeDrawer();
},
onmouseover: e => {
vnode.state.mouseIn = true;
resizeDrawer();
e.redraw = false;
},
onmouseleave: e => {
vnode.state.mouseIn = false;
resizeDrawer();
e.redraw = false;
},
onupdate: resizeDrawer,
onbeforeremove: vnode2 => {
vnode2.dom.onmouseover = vnode2.dom.onmouseleave = null;
vnode2.dom.style.opacity = 0;
vnode2.dom.style.height = 0;
return new Promise(resolve => {
setTimeout(resolve, 500);
});
}
},
"Commit"
),
statusElement,
stagingElements.length ? stagingElements : m(".queue", queueElements)
);
}
return m(
"div.drawer-wrapper",
drawerElement,
m(
"button",
"div.notifications-wrapper",
{
title: "Clear tasks",
onclick: taskQueue.clear,
disabled: !queueElements.length
key: "notifications",
style: "position: relative;",
onupdate: repositionNotifications,
oncreate: repositionNotifications
},
"Clear"
notificationElements
)
);
statusElement = m(
".status",
m(
"span.queued",
{ class: statusCount.queued ? "active" : "" },
`Queued: ${statusCount.queued}`
),
m(
"span.pending",
{ class: statusCount.pending ? "active" : "" },
`Pending: ${statusCount.pending}`
),
m(
"span.fault",
{ class: statusCount.fault ? "active" : "" },
`Fault: ${statusCount.fault}`
),
m(
"span.stale",
{ class: statusCount.stale ? "active" : "" },
`Stale: ${statusCount.stale}`
),
actions
);
drawerElement = m(
".drawer",
{
key: "drawer",
style: "opacity: 0;height: 0;",
oncreate: vnode2 => {
vnode.state.mouseIn = false;
vnode2.dom.style.opacity = 1;
resizeDrawer();
},
onmouseover: e => {
vnode.state.mouseIn = true;
resizeDrawer();
e.redraw = false;
},
onmouseleave: e => {
vnode.state.mouseIn = false;
resizeDrawer();
e.redraw = false;
},
onupdate: resizeDrawer,
onbeforeremove: vnode2 => {
vnode2.dom.onmouseover = vnode2.dom.onmouseleave = null;
vnode2.dom.style.opacity = 0;
vnode2.dom.style.height = 0;
return new Promise(resolve => {
setTimeout(resolve, 500);
});
}
},
statusElement,
stagingElements.length ? stagingElements : m(".queue", queueElements)
);
}
return m(
"div.drawer-wrapper",
drawerElement,
m(
"div.notifications-wrapper",
{
key: "notifications",
style: "position: relative;",
onupdate: repositionNotifications,
oncreate: repositionNotifications
},
notificationElements
)
);
}
};
export default component;
};
}
+9 -9
View File
@@ -1,12 +1,12 @@
"use strict";
import m from "mithril";
import { m } from "./components";
const component = {
view: function(vnode) {
document.title = "Error! - GenieACS";
return m("p.error", vnode.attrs.error);
}
};
export { component };
export function component() {
return {
view: function(vnode) {
document.title = "Error! - GenieACS";
return m("p.error", vnode.attrs.error);
}
};
}
+69 -71
View File
@@ -1,6 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as store from "./store";
@@ -9,7 +9,6 @@ import memoize from "../lib/common/memoize";
import * as smartQuery from "./smart-query";
import { map, parse } from "../lib/common/expression-parser";
import { loadYaml, yaml } from "./dynamic-loader";
import longTextComponent from "./long-text-component";
const PAGE_SIZE = config.ui.pageSize || 10;
@@ -47,7 +46,7 @@ const unpackSmartQuery = memoize(query => {
});
});
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("faults", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -136,17 +135,13 @@ function renderTable(
const tds = [m("td", checkbox)];
for (const attr of attributes) {
if (attr.id === "device") {
if (attr.id === "device")
tds.push(m("td", m("a", { href: deviceHref }, f[attr.id])));
} else if (attr.id === "timestamp") {
else if (attr.id === "timestamp")
tds.push(m("td", new Date(f[attr.id]).toLocaleString()));
} else if (attr.id === "detail") {
tds.push(
m("td", m(longTextComponent, { text: yaml.stringify(f[attr.id]) }))
);
} else {
tds.push(m("td", f[attr.id]));
}
else if (attr.id === "detail")
tds.push(m("td", m("long-text", { text: yaml.stringify(f[attr.id]) })));
else tds.push(m("td", f[attr.id]));
}
rows.push(
@@ -234,64 +229,67 @@ function renderTable(
];
}
const component = {
view: vnode => {
document.title = "Faults - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Faults - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/faults", ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/faults", ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const faults = store.fetch("faults", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("faults", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const f of faults.value)
if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]);
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing faults"),
m(filterComponent, {
resource: "faults",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
faults,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set("/faults", ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set("/faults", ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const faults = store.fetch("faults", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("faults", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const f of faults.value)
if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]);
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing faults"),
m(filterComponent, {
resource: "faults",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
faults,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
};
export { init, component };
};
}
+65 -63
View File
@@ -1,7 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as store from "./store";
@@ -110,7 +109,7 @@ const getDownloadUrl = memoize(filter => {
})}`;
});
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("files", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -319,65 +318,68 @@ function renderTable(
];
}
const component = {
view: vnode => {
document.title = "Files - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Files - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const files = store.fetch("files", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("files", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const file of files.value)
if (vnode.state.selected.has(file["_id"])) selected.add(file["_id"]);
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing files"),
m(filterComponent, {
resource: "files",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
files,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const files = store.fetch("files", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("files", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const file of files.value)
if (vnode.state.selected.has(file["_id"])) selected.add(file["_id"]);
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing files"),
m(filterComponent, {
resource: "files",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
files,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
};
export { init, component };
};
}
+55 -52
View File
@@ -56,60 +56,63 @@ const splitFilter = memoize(filter => {
return list;
});
const component = {
view: vnode => {
if (!vnode.state.filterList || vnode.attrs.filter !== vnode.state.filter) {
vnode.state.filterInvalid = 0;
vnode.state.filter = vnode.attrs.filter;
vnode.state.filterList = splitFilter(vnode.attrs.filter);
}
export default function component() {
return {
view: vnode => {
if (
!vnode.state.filterList ||
vnode.attrs.filter !== vnode.state.filter
) {
vnode.state.filterInvalid = 0;
vnode.state.filter = vnode.attrs.filter;
vnode.state.filterList = splitFilter(vnode.attrs.filter);
}
function onChange() {
vnode.state.filterInvalid = 0;
vnode.state.filterList = vnode.state.filterList.filter(f => f);
let filter = vnode.state.filterList.map((f, idx) => {
try {
return parseFilter(vnode.attrs.resource, f);
} catch (err) {
vnode.state.filterInvalid |= 1 << idx;
}
return null;
});
vnode.state.filterList.push("");
function onChange() {
vnode.state.filterInvalid = 0;
vnode.state.filterList = vnode.state.filterList.filter(f => f);
let filter = vnode.state.filterList.map((f, idx) => {
try {
return parseFilter(vnode.attrs.resource, f);
} catch (err) {
vnode.state.filterInvalid |= 1 << idx;
}
return null;
});
vnode.state.filterList.push("");
if (!vnode.state.filterInvalid) {
delete vnode.state.filter;
if (filter.length === 0) {
vnode.attrs.onChange("");
} else {
if (filter.length > 1) filter = ["AND"].concat(filter);
else filter = filter[0];
vnode.attrs.onChange(stringify(filter));
if (!vnode.state.filterInvalid) {
delete vnode.state.filter;
if (filter.length === 0) {
vnode.attrs.onChange("");
} else {
if (filter.length > 1) filter = ["AND"].concat(filter);
else filter = filter[0];
vnode.attrs.onChange(stringify(filter));
}
}
}
return m(
"div.filter",
[m("b", "Filter")].concat(
vnode.state.filterList.map((fltr, idx) => {
return m("input", {
type: "text",
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
value: fltr,
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);
}
});
})
)
);
}
return m(
"div.filter",
[m("b", "Filter")].concat(
vnode.state.filterList.map((fltr, idx) => {
return m("input", {
type: "text",
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
value: fltr,
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);
}
});
})
)
);
}
};
export default component;
};
}
+25 -26
View File
@@ -1,7 +1,6 @@
"use strict";
import m from "mithril";
import menu from "./menu";
import drawerComponent from "./drawer-component";
import userMenu from "./user-menu";
@@ -16,29 +15,29 @@ const adminPages = [
"config"
];
const layout = {
view: vnode => {
let sideMenu, group;
if (adminPages.includes(vnode.attrs.page)) {
group = "admin";
sideMenu = m(adminMenu, { page: vnode.attrs.page });
export default function layout() {
return {
view: vnode => {
let sideMenu, group;
if (adminPages.includes(vnode.attrs.page)) {
group = "admin";
sideMenu = m(adminMenu, { page: vnode.attrs.page });
}
return [
m("#header", [
m("img.logo", { src: "logo.svg" }),
m(userMenu),
m(menu, { page: group || vnode.attrs.page }),
m(drawerComponent)
]),
m(
"#content-wrapper",
sideMenu,
m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
),
overlay.render()
];
}
return [
m("#header", [
m("img.logo", { src: "logo.svg" }),
m(userMenu),
m(menu, { page: group || vnode.attrs.page }),
m(drawerComponent)
]),
m(
"#content-wrapper",
sideMenu,
m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
),
overlay.render()
];
}
};
export default layout;
};
}
+58 -59
View File
@@ -1,72 +1,71 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import * as store from "./store";
import * as notifications from "./notifications";
function init(args) {
export function init(args) {
return Promise.resolve(args);
}
const component = {
view: vnode => {
if (window.username) m.route.set(vnode.attrs.continue || "/");
export function component() {
return {
view: vnode => {
if (window.username) m.route.set(vnode.attrs.continue || "/");
document.title = "Login - GenieACS";
return [
m("h1", "Log in"),
m(
"form",
document.title = "Login - GenieACS";
return [
m("h1", "Log in"),
m(
"p",
m("label", { for: "username" }, "Username"),
m("input", {
name: "username",
type: "text",
value: vnode.state.username,
oncreate: vnode2 => {
vnode2.dom.focus();
},
oninput: m.withAttr("value", v => (vnode.state.username = v))
})
),
m(
"p",
m("label", { for: "password" }, "Password"),
m("input", {
name: "password",
type: "password",
value: vnode.state.password,
oninput: m.withAttr("value", v => (vnode.state.password = v))
})
),
m(
"p",
"form",
m(
"button.primary",
{
type: "submit",
onclick: e => {
e.target.disabled = true;
store
.logIn(vnode.state.username, vnode.state.password)
.then(() => {
location.reload();
})
.catch(err => {
notifications.push("error", err.message);
e.target.disabled = false;
});
return false;
}
},
"Login"
"p",
m("label", { for: "username" }, "Username"),
m("input", {
name: "username",
type: "text",
value: vnode.state.username,
oncreate: vnode2 => {
vnode2.dom.focus();
},
oninput: m.withAttr("value", v => (vnode.state.username = v))
})
),
m(
"p",
m("label", { for: "password" }, "Password"),
m("input", {
name: "password",
type: "password",
value: vnode.state.password,
oninput: m.withAttr("value", v => (vnode.state.password = v))
})
),
m(
"p",
m(
"button.primary",
{
type: "submit",
onclick: e => {
e.target.disabled = true;
store
.logIn(vnode.state.username, vnode.state.password)
.then(() => {
location.reload();
})
.catch(err => {
notifications.push("error", err.message);
e.target.disabled = false;
});
return false;
}
},
"Login"
)
)
)
)
];
}
};
export { init, component };
];
}
};
}
+48 -48
View File
@@ -3,54 +3,54 @@
import m from "mithril";
import * as overlay from "./overlay";
const component = {
view: vnode => {
const text = vnode.attrs.text;
const element = vnode.attrs.element || "span";
export default function component() {
return {
view: vnode => {
const text = vnode.attrs.text;
const element = vnode.attrs.element || "span";
function overflowed(_vnode) {
if (_vnode.dom.classList.contains("long-text-overflowed")) return;
_vnode.dom.classList.add("long-text-overflowed");
_vnode.dom.onclick = e => {
overlay.open(() => {
return m("textarea.long-text", {
value: text,
cols: 80,
rows: 24,
readonly: "",
oncreate: vnode2 => {
vnode2.dom.focus();
vnode2.dom.select();
}
function overflowed(_vnode) {
if (_vnode.dom.classList.contains("long-text-overflowed")) return;
_vnode.dom.classList.add("long-text-overflowed");
_vnode.dom.onclick = e => {
overlay.open(() => {
return m("textarea.long-text", {
value: text,
cols: 80,
rows: 24,
readonly: "",
oncreate: vnode2 => {
vnode2.dom.focus();
vnode2.dom.select();
}
});
});
});
// prevent index page selection
e.stopPropagation();
m.redraw();
};
// prevent index page selection
e.stopPropagation();
m.redraw();
};
}
return m(
element,
{
oncreate: vnode2 => {
if (vnode2.dom.clientWidth !== vnode2.dom.scrollWidth)
overflowed(vnode2);
},
onupdate: vnode2 => {
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
vnode2.dom.classList.remove("long-text-overflowed");
vnode2.dom.onclick = null;
} else {
overflowed(vnode2);
}
},
class: "long-text",
title: text
},
text
);
}
return m(
element,
{
oncreate: vnode2 => {
if (vnode2.dom.clientWidth !== vnode2.dom.scrollWidth)
overflowed(vnode2);
},
onupdate: vnode2 => {
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
vnode2.dom.classList.remove("long-text-overflowed");
vnode2.dom.onclick = null;
} else {
overflowed(vnode2);
}
},
class: "long-text",
title: text
},
text
);
}
};
export default component;
};
}
+51 -46
View File
@@ -2,57 +2,62 @@
import m from "mithril";
const menu = {
view: vnode => {
const active = { [vnode.attrs.page]: "active" };
export default function menu() {
return {
view: vnode => {
const active = { [vnode.attrs.page]: "active" };
const tabs = [];
if (window.authorizer.hasAccess("devices", 1)) {
tabs.push(
m(
"li",
{ class: active["overview"] },
m("a", { href: "#!/overview" }, "Overview")
)
);
}
if (window.authorizer.hasAccess("devices", 2)) {
tabs.push(
m(
"li",
{ class: active["devices"] },
m("a", { href: "#!/devices" }, "Devices")
)
);
}
if (window.authorizer.hasAccess("faults", 2)) {
tabs.push(
m(
"li",
{ class: active["faults"] },
m("a", { href: "#!/faults" }, "Faults")
)
);
}
const adminPages = ["presets", "provisions", "virtualParameters", "files"];
for (const page of adminPages) {
if (window.authorizer.hasAccess(page, 2)) {
const tabs = [];
if (window.authorizer.hasAccess("devices", 1)) {
tabs.push(
m(
"li",
{ class: active["admin"] },
m("a", { href: "#!/admin" }, "Admin")
{ class: active["overview"] },
m("a", { href: "#!/overview" }, "Overview")
)
);
break;
}
if (window.authorizer.hasAccess("devices", 2)) {
tabs.push(
m(
"li",
{ class: active["devices"] },
m("a", { href: "#!/devices" }, "Devices")
)
);
}
if (window.authorizer.hasAccess("faults", 2)) {
tabs.push(
m(
"li",
{ class: active["faults"] },
m("a", { href: "#!/faults" }, "Faults")
)
);
}
const adminPages = [
"presets",
"provisions",
"virtualParameters",
"files"
];
for (const page of adminPages) {
if (window.authorizer.hasAccess(page, 2)) {
tabs.push(
m(
"li",
{ class: active["admin"] },
m("a", { href: "#!/admin" }, "Admin")
)
);
break;
}
}
return m("nav", m("ul", tabs));
}
return m("nav", m("ul", tabs));
}
};
export default menu;
};
}
+2 -4
View File
@@ -4,7 +4,7 @@ import m from "mithril";
const notifications = new Set();
function push(type, message) {
export function push(type, message) {
const n = { type: type, message: message, timestamp: Date.now() };
notifications.add(n);
m.redraw();
@@ -14,8 +14,6 @@ function push(type, message) {
}, 4000);
}
function getNotifications() {
export function getNotifications() {
return notifications;
}
export { push, getNotifications };
+23 -23
View File
@@ -1,6 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import * as store from "./store";
import pieChartComponent from "./pie-chart-component";
@@ -26,7 +26,7 @@ function queryCharts(charts) {
return charts;
}
const init = function() {
export function init() {
if (!window.authorizer.hasAccess("devices", 1)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -34,31 +34,31 @@ const init = function() {
}
return Promise.resolve({ charts: queryCharts(CHARTS) });
};
}
const component = {
view: vnode => {
document.title = "Overview - GenieACS";
const children = [];
for (const group of Object.values(GROUPS)) {
if (group.label) children.push(m("h1", group.label));
export function component() {
return {
view: vnode => {
document.title = "Overview - GenieACS";
const children = [];
for (const group of Object.values(GROUPS)) {
if (group.label) children.push(m("h1", group.label));
const groupChildren = [];
for (const chartName of Object.values(group.charts)) {
const chart = vnode.attrs.charts[chartName];
const chartChildren = [];
if (chart.label) chartChildren.push(m("h2", chart.label));
const groupChildren = [];
for (const chartName of Object.values(group.charts)) {
const chart = vnode.attrs.charts[chartName];
const chartChildren = [];
if (chart.label) chartChildren.push(m("h2", chart.label));
chartChildren.push(m(pieChartComponent, { chart: chart }));
chartChildren.push(m(pieChartComponent, { chart: chart }));
groupChildren.push(m(".overview-chart", chartChildren));
groupChildren.push(m(".overview-chart", chartChildren));
}
children.push(m(".overview-chart-group", groupChildren));
}
children.push(m(".overview-chart-group", groupChildren));
return children;
}
return children;
}
};
export { init, component };
};
}
+8 -8
View File
@@ -1,6 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import { stringify } from "../lib/common/expression-parser";
import memoize from "../lib/common/memoize";
@@ -115,10 +115,10 @@ const drawChart = function(chartData) {
]);
};
const component = {
view: vnode => {
return drawChart(vnode.attrs.chart);
}
};
export default component;
export default function component() {
return {
view: vnode => {
return drawChart(vnode.attrs.chart);
}
};
}
+86 -84
View File
@@ -1,7 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as overlay from "./overlay";
@@ -108,7 +107,7 @@ const getDownloadUrl = memoize(filter => {
})}`;
});
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("presets", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -383,87 +382,90 @@ function renderTable(
];
}
const component = {
view: vnode => {
document.title = "Presets - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Presets - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const presets = store.fetch("presets", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("presets", filter);
const provisions = store.fetch("provisions", true);
if (provisions.fulfilled) {
const provisionAttr = attributes.find(attr => {
return attr.id === "provision";
});
const provisionIds = new Set([
"refresh",
"value",
"tag",
"reboot",
"reset",
"download",
"instances"
]);
for (const p of provisions.value) provisionIds.add(p["_id"]);
provisionAttr.options = Array.from(provisionIds);
}
const selected = new Set();
if (vnode.state.selected) {
for (const preset of presets.value) {
if (vnode.state.selected.has(preset["_id"]))
selected.add(preset["_id"]);
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const presets = store.fetch("presets", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("presets", filter);
const provisions = store.fetch("provisions", true);
if (provisions.fulfilled) {
const provisionAttr = attributes.find(attr => {
return attr.id === "provision";
});
const provisionIds = new Set([
"refresh",
"value",
"tag",
"reboot",
"reset",
"download",
"instances"
]);
for (const p of provisions.value) provisionIds.add(p["_id"]);
provisionAttr.options = Array.from(provisionIds);
}
const selected = new Set();
if (vnode.state.selected) {
for (const preset of presets.value) {
if (vnode.state.selected.has(preset["_id"]))
selected.add(preset["_id"]);
}
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing presets"),
m(filterComponent, {
resource: "presets",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
presets,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing presets"),
m(filterComponent, {
resource: "presets",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
presets,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
};
export { init, component };
};
}
+66 -64
View File
@@ -1,7 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as store from "./store";
@@ -98,7 +97,7 @@ const getDownloadUrl = memoize(filter => {
})}`;
});
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("provisions", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -371,67 +370,70 @@ function renderTable(
];
}
const component = {
view: vnode => {
document.title = "Provisions - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Provisions - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const provisions = store.fetch("provisions", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("provisions", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const provision of provisions.value) {
if (vnode.state.selected.has(provision["_id"]))
selected.add(provision["_id"]);
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const provisions = store.fetch("provisions", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("provisions", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const provision of provisions.value) {
if (vnode.state.selected.has(provision["_id"]))
selected.add(provision["_id"]);
}
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing provisions"),
m(filterComponent, {
resource: "provisions",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
provisions,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing provisions"),
m(filterComponent, {
resource: "provisions",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
provisions,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
};
export { init, component };
};
}
+78 -77
View File
@@ -1,6 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import { codeMirror } from "./dynamic-loader";
const singular = {
@@ -93,91 +93,92 @@ function createField(current, attr, focus) {
});
}
const component = {
view: vnode => {
const actionHandler = vnode.attrs.actionHandler;
const attributes = vnode.attrs.attributes;
const resource = vnode.attrs.resource;
const base = vnode.attrs.base || {};
if (!vnode.state.current) {
vnode.state.current = {
isNew: !base["_id"],
object: Object.assign({}, base)
};
}
export default function component() {
return {
view: vnode => {
const actionHandler = vnode.attrs.actionHandler;
const attributes = vnode.attrs.attributes;
const resource = vnode.attrs.resource;
const base = vnode.attrs.base || {};
if (!vnode.state.current) {
vnode.state.current = {
isNew: !base["_id"],
object: Object.assign({}, base)
};
}
const current = vnode.state.current;
const current = vnode.state.current;
const form = [];
let focused = false;
for (const attr of attributes) {
let focus = false;
if (!focused && (current.isNew || attr.id !== "_id"))
focus = focused = true;
const form = [];
let focused = false;
for (const attr of attributes) {
let focus = false;
if (!focused && (current.isNew || attr.id !== "_id"))
focus = focused = true;
form.push(
form.push(
m(
"p",
m("label", { for: attr.id }, attr.label || attr.id),
m("br"),
createField(current, attr, focus)
)
);
}
const submit = m("button.primary", { type: "submit" }, "Save");
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"
)
);
}
form.push(m(".actions-bar", buttons));
const children = [
m(
"p",
m("label", { for: attr.id }, attr.label || attr.id),
m("br"),
createField(current, attr, focus)
)
);
}
const submit = m("button.primary", { type: "submit" }, "Save");
const buttons = [submit];
if (!current.isNew) {
buttons.push(
"h1",
`${current.isNew ? "New" : "Editing"} ${singular[resource] ||
resource}`
),
m(
"button.primary",
"form",
{
type: "button",
title: `Delete ${singular[resource] || resource}`,
onclick: e => {
onsubmit: e => {
e.redraw = false;
e.target.disabled = true;
actionHandler("delete", current.object).then(() => {
e.target.disabled = false;
// const onsubmit = e.target.onsubmit;
e.preventDefault();
// e.target.onsubmit = null;
submit.dom.disabled = true;
// submit.dom.textContent = "Loading ...";
actionHandler("save", current.object).then(() => {
// submit.dom.textContent = "Save";
// e.target.onsubmit = onsubmit;
submit.dom.disabled = false;
});
}
},
"Delete"
form
)
);
];
return m("div.put-form", children);
}
form.push(m(".actions-bar", buttons));
const children = [
m(
"h1",
`${current.isNew ? "New" : "Editing"} ${singular[resource] || resource}`
),
m(
"form",
{
onsubmit: e => {
e.redraw = false;
// const onsubmit = e.target.onsubmit;
e.preventDefault();
// e.target.onsubmit = null;
submit.dom.disabled = true;
// submit.dom.textContent = "Loading ...";
actionHandler("save", current.object).then(() => {
// submit.dom.textContent = "Save";
// e.target.onsubmit = onsubmit;
submit.dom.disabled = false;
});
}
},
form
)
];
return m("div.put-form", children);
}
};
export default component;
};
}
+43 -42
View File
@@ -5,46 +5,47 @@ import m from "mithril";
import * as store from "./store";
import * as notifications from "./notifications";
const userMenu = {
view: () => {
if (window.username) {
return m(
"div.user-menu",
window.username,
m(
"button",
{
onclick: e => {
e.target.disabled = true;
store
.logOut()
.then(() => {
location.hash = "";
location.reload();
})
.catch(err => {
e.target.disabled = false;
notifications.push("error", err.message);
});
return false;
}
},
"Log out"
)
);
} else {
return m(
"div.user-menu",
m(
"a",
{
href: "#!/login?" + m.buildQueryString({ continue: m.route.get() })
},
"Log in"
)
);
export default function userMenu() {
return {
view: () => {
if (window.username) {
return m(
"div.user-menu",
window.username,
m(
"button",
{
onclick: e => {
e.target.disabled = true;
store
.logOut()
.then(() => {
location.hash = "";
location.reload();
})
.catch(err => {
e.target.disabled = false;
notifications.push("error", err.message);
});
return false;
}
},
"Log out"
)
);
} else {
return m(
"div.user-menu",
m(
"a",
{
href:
"#!/login?" + m.buildQueryString({ continue: m.route.get() })
},
"Log in"
)
);
}
}
}
};
export default userMenu;
};
}
+66 -64
View File
@@ -1,7 +1,6 @@
"use strict";
import m from "mithril";
import { m } from "./components";
import config from "./config";
import filterComponent from "./filter-component";
import * as store from "./store";
@@ -98,7 +97,7 @@ const getDownloadUrl = memoize(filter => {
})}`;
});
function init(args) {
export function init(args) {
if (!window.authorizer.hasAccess("virtualParameters", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
@@ -381,67 +380,70 @@ function renderTable(
];
}
const component = {
view: vnode => {
document.title = "Virtual Parameters - GenieACS";
export function component() {
return {
view: vnode => {
document.title = "Virtual Parameters - GenieACS";
function showMore() {
vnode.state.showCount = (vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter ? memoizedParse(vnode.attrs.filter) : true;
filter = unpackSmartQuery(filter);
const virtualParameters = store.fetch("virtualParameters", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("virtualParameters", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const virtualParameter of virtualParameters.value) {
if (vnode.state.selected.has(virtualParameter["_id"]))
selected.add(virtualParameter["_id"]);
function showMore() {
vnode.state.showCount =
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
m.redraw();
}
function onFilterChanged(filter) {
const ops = { filter };
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
m.route.set(m.route.get(), ops);
}
function onSortChange(sort) {
const ops = { sort };
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
m.route.set(m.route.get(), ops);
}
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
let filter = vnode.attrs.filter
? memoizedParse(vnode.attrs.filter)
: true;
filter = unpackSmartQuery(filter);
const virtualParameters = store.fetch("virtualParameters", filter, {
limit: vnode.state.showCount || PAGE_SIZE,
sort: sort
});
const count = store.count("virtualParameters", filter);
const selected = new Set();
if (vnode.state.selected) {
for (const virtualParameter of virtualParameters.value) {
if (vnode.state.selected.has(virtualParameter["_id"]))
selected.add(virtualParameter["_id"]);
}
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing virtual parameters"),
m(filterComponent, {
resource: "virtualParameters",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
virtualParameters,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
vnode.state.selected = selected;
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
return [
m("h1", "Listing virtual parameters"),
m(filterComponent, {
resource: "virtualParameters",
filter: vnode.attrs.filter,
onChange: onFilterChanged
}),
renderTable(
virtualParameters,
count.value,
selected,
showMore,
downloadUrl,
sort,
onSortChange
)
];
}
};
export { init, component };
};
}