Refactor components
Implement context mechanism to pass default attributes to descendant components.
This commit is contained in:
1 parent
d46d133878
commit
ee8c43747e
35 files changed
+1903
-1831
No files matched your search
+58
-58
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 };
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user