TypeScript!
This commit is contained in:
1 parent
ee8c43747e
commit
5d3c81cb23
105 files changed
+12054
-1901
No files matched your search
@@ -2,9 +2,5 @@
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"allowImportExportEverywhere": true
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
|
||||
export default function menu() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const active = { [vnode.attrs.page]: "active" };
|
||||
const active = { [vnode.attrs["page"]]: "active" };
|
||||
const tabs = [];
|
||||
|
||||
if (window.authorizer.hasAccess("presets", 1)) {
|
||||
@@ -61,4 +60,6 @@ export default function menu() {
|
||||
return m("nav#side-menu", m("ul", tabs));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
+20
-11
@@ -1,7 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import m, { RouteResolver } from "mithril";
|
||||
import layout from "./layout";
|
||||
import * as store from "./store";
|
||||
import * as loginPage from "./login-page";
|
||||
@@ -19,6 +16,15 @@ import Authorizer from "../lib//common/authorizer";
|
||||
import * as notifications from "./notifications";
|
||||
import { contextifyComponent } from "./components";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
authorizer: Authorizer;
|
||||
permissionSets: any;
|
||||
username: string;
|
||||
clientConfig: any;
|
||||
}
|
||||
}
|
||||
|
||||
window.authorizer = new Authorizer(window.permissionSets);
|
||||
|
||||
const adminPages = [
|
||||
@@ -35,7 +41,7 @@ let lastRenderTimestamp = 0;
|
||||
let pageVisitTimestamp = 0;
|
||||
let fulfillTimeout = null;
|
||||
|
||||
function fulfill() {
|
||||
function fulfill(): void {
|
||||
clearTimeout(fulfillTimeout);
|
||||
fulfillTimeout = setTimeout(() => {
|
||||
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => {
|
||||
@@ -44,16 +50,19 @@ function fulfill() {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function pagify(pageName, page) {
|
||||
const component = {
|
||||
function pagify(pageName, page): RouteResolver {
|
||||
const component: RouteResolver = {
|
||||
render: () => {
|
||||
lastRenderTimestamp = Date.now();
|
||||
let p;
|
||||
if (state && state.error) p = m(errorPage.component, state);
|
||||
else p = m(contextifyComponent(page.component), state);
|
||||
fulfill();
|
||||
return m(layout, { page: pageName }, p);
|
||||
}
|
||||
const attrs = {};
|
||||
attrs["page"] = pageName;
|
||||
return m(layout, attrs, p);
|
||||
},
|
||||
onmatch: null
|
||||
};
|
||||
|
||||
component.onmatch = (args, requestedPath) => {
|
||||
@@ -87,8 +96,8 @@ function pagify(pageName, page) {
|
||||
return component;
|
||||
}
|
||||
|
||||
function redirectAdminPage() {
|
||||
const component = {
|
||||
function redirectAdminPage(): RouteResolver {
|
||||
const component: RouteResolver = {
|
||||
onmatch: () => {
|
||||
for (const page of adminPages) {
|
||||
if (window.authorizer.hasAccess(page, 2)) {
|
||||
@@ -1,7 +1,16 @@
|
||||
"use strict";
|
||||
export default class Autocomplete {
|
||||
private callback: (
|
||||
value: string,
|
||||
callback: (suggestions: string[]) => void
|
||||
) => void;
|
||||
private element: HTMLInputElement;
|
||||
private hideTimeout: NodeJS.Timeout;
|
||||
private visible: boolean;
|
||||
private default: string;
|
||||
private selection: number;
|
||||
private container: HTMLElement;
|
||||
|
||||
class Autocomplete {
|
||||
constructor(className, callback) {
|
||||
public constructor(className, callback) {
|
||||
this.callback = callback;
|
||||
this.element = null;
|
||||
this.hideTimeout = null;
|
||||
@@ -12,11 +21,11 @@ class Autocomplete {
|
||||
this.container = document.createElement("div");
|
||||
this.container.style.position = "absolute";
|
||||
this.container.style.display = "block";
|
||||
this.container.style.opacity = 0;
|
||||
this.container.style.opacity = "0";
|
||||
this.container.className = className;
|
||||
}
|
||||
|
||||
attach(el) {
|
||||
public attach(el): void {
|
||||
el.setAttribute("autocomplete", "off");
|
||||
|
||||
el.addEventListener("focus", () => {
|
||||
@@ -63,8 +72,8 @@ class Autocomplete {
|
||||
});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.container.style.opacity = 0;
|
||||
private hide(): void {
|
||||
this.container.style.opacity = "0";
|
||||
this.visible = false;
|
||||
this.default = null;
|
||||
this.selection = null;
|
||||
@@ -77,7 +86,7 @@ class Autocomplete {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
update() {
|
||||
private update(): void {
|
||||
const el = this.element;
|
||||
|
||||
this.callback(el.value, suggestions => {
|
||||
@@ -101,7 +110,7 @@ class Autocomplete {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.hideTimeout = null;
|
||||
}
|
||||
this.container.style.opacity = 1;
|
||||
this.container.style.opacity = "1";
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
@@ -128,7 +137,7 @@ class Autocomplete {
|
||||
e.addEventListener("mousedown", ev => {
|
||||
ev.preventDefault();
|
||||
el.value = suggestion;
|
||||
if (this.element === el) this.update(el);
|
||||
if (this.element === el) this.update();
|
||||
});
|
||||
this.container.appendChild(e);
|
||||
}
|
||||
@@ -150,5 +159,3 @@ class Autocomplete {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Autocomplete;
|
||||
@@ -1,6 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import m, {
|
||||
Static,
|
||||
Attributes,
|
||||
Children,
|
||||
ComponentTypes,
|
||||
Lifecycle,
|
||||
ClosureComponent,
|
||||
Vnode
|
||||
} from "mithril";
|
||||
import parameter from "./components/parameter";
|
||||
import parameterList from "./components/parameter-list";
|
||||
import parameterTable from "./components/parameter-table";
|
||||
@@ -31,13 +37,33 @@ const comps = {
|
||||
"long-text": longTextComponent
|
||||
};
|
||||
|
||||
const contextifiedComponents = new WeakMap();
|
||||
const vnodeContext = new WeakMap();
|
||||
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
||||
const vnodeContext = new WeakMap<Vnode, Attributes>();
|
||||
|
||||
function contextFn(context, ...argumentsList) {
|
||||
const vnode = Reflect.apply(M, undefined, argumentsList);
|
||||
vnodeContext.set(vnode, context);
|
||||
return vnode;
|
||||
interface MC extends Static {
|
||||
context: {
|
||||
(ctx: Attributes, selector: string, ...children: Children[]): Vnode<
|
||||
any,
|
||||
any
|
||||
>;
|
||||
(
|
||||
ctx: Attributes,
|
||||
selector: string,
|
||||
attributes: Attributes,
|
||||
...children: Children[]
|
||||
): Vnode<any, any>;
|
||||
<Attrs, State>(
|
||||
ctx: Attributes,
|
||||
component: ComponentTypes<Attrs, State>,
|
||||
...args: Children[]
|
||||
): Vnode<Attrs, State>;
|
||||
<Attrs, State>(
|
||||
ctx: Attributes,
|
||||
component: ComponentTypes<Attrs, State>,
|
||||
attributes: Attrs & Lifecycle<Attrs, State> & { key?: string | number },
|
||||
...args: Children[]
|
||||
): Vnode<Attrs, State>;
|
||||
};
|
||||
}
|
||||
|
||||
const M = new Proxy(m, {
|
||||
@@ -52,9 +78,15 @@ const M = new Proxy(m, {
|
||||
if (prop === "context") return contextFn;
|
||||
else return Reflect.get(target, prop);
|
||||
}
|
||||
});
|
||||
}) as MC;
|
||||
|
||||
function applyContext(vnode, parentContext) {
|
||||
function contextFn(context, ...argumentsList): Vnode {
|
||||
const vnode = Reflect.apply(M, undefined, argumentsList);
|
||||
vnodeContext.set(vnode, context);
|
||||
return vnode;
|
||||
}
|
||||
|
||||
function applyContext(vnode, parentContext): void {
|
||||
if (Array.isArray(vnode)) {
|
||||
for (const c of vnode) applyContext(c, parentContext);
|
||||
} else if (vnode && typeof vnode === "object" && vnode.tag) {
|
||||
@@ -66,25 +98,25 @@ function applyContext(vnode, parentContext) {
|
||||
}
|
||||
}
|
||||
|
||||
export function contextifyComponent(component) {
|
||||
export function contextifyComponent(component: ComponentTypes): ComponentTypes {
|
||||
let c = contextifiedComponents.get(component);
|
||||
if (!c) {
|
||||
if (typeof component !== "function") {
|
||||
c = Object.assign({}, component);
|
||||
const view = component.view;
|
||||
c.view = vnode => {
|
||||
c.view = function(vnode) {
|
||||
const context = vnodeContext.get(vnode) || {};
|
||||
const res = view(vnode, M);
|
||||
const res = Reflect.apply(view, this, [vnode]);
|
||||
applyContext(res, context);
|
||||
return res;
|
||||
};
|
||||
} else if (!component.prototype.view) {
|
||||
} else if (!component.prototype || !component.prototype.view) {
|
||||
c = initialNode => {
|
||||
const state = component(initialNode);
|
||||
const state = (component as ClosureComponent)(initialNode);
|
||||
const view = state.view;
|
||||
state.view = vnode => {
|
||||
state.view = function(vnode) {
|
||||
const context = vnodeContext.get(vnode) || {};
|
||||
const res = view(vnode, M);
|
||||
const res = Reflect.apply(view, this, [vnode]);
|
||||
applyContext(res, context);
|
||||
return res;
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import { parse } from "../../lib/common/expression-parser";
|
||||
@@ -7,30 +6,30 @@ import memoize from "../../lib/common/memoize";
|
||||
|
||||
const memoizedParse = memoize(parse);
|
||||
|
||||
function escapeRegExp(str) {
|
||||
function escapeRegExp(str): string {
|
||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||
}
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const search = m("input", {
|
||||
type: "text",
|
||||
placeholder: "Search parameters",
|
||||
oninput: e => {
|
||||
vnode.state.searchString = e.target.value;
|
||||
vnode.state["searchString"] = e.target.value;
|
||||
e.redraw = false;
|
||||
clearTimeout(vnode.state.timeout);
|
||||
vnode.state.timeout = setTimeout(m.redraw, 500);
|
||||
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 (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");
|
||||
}
|
||||
@@ -43,7 +42,7 @@ export default function component() {
|
||||
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 (!re.test(str)) attrs["style"] = "display: none;";
|
||||
}
|
||||
|
||||
if (p.object === false) {
|
||||
@@ -132,4 +131,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,22 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
const component: ClosureComponent = (): 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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,14 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as notifications from "../notifications";
|
||||
import * as store from "../store";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const buttons = [];
|
||||
|
||||
@@ -87,4 +86,6 @@ export default function component() {
|
||||
return m(".actions-bar", buttons);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,13 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
import * as notifications from "../notifications";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const faults = store.fetch("faults", [
|
||||
"AND",
|
||||
@@ -72,4 +71,6 @@ export default function component() {
|
||||
return m("table.table", thead, tbody);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,18 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
let deviceId;
|
||||
if (vnode.attrs.device)
|
||||
deviceId = vnode.attrs.device["DeviceID.ID"].value[0];
|
||||
if (vnode.attrs["device"])
|
||||
deviceId = vnode.attrs["device"]["DeviceID.ID"].value[0];
|
||||
|
||||
const children = Object.values(vnode.attrs.components).map(c => {
|
||||
const children = Object.values(vnode.attrs["components"]).map(c => {
|
||||
if (typeof c !== "object") return `${c}`;
|
||||
const attrs = Object.assign({}, vnode.attrs, c);
|
||||
return m(attrs.type, attrs);
|
||||
return m(attrs["type"], attrs);
|
||||
});
|
||||
if (deviceId) {
|
||||
return m(
|
||||
@@ -25,4 +24,6 @@ export default function component() {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,18 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import config from "../config";
|
||||
import * as store from "../store";
|
||||
|
||||
const CHARTS = config.ui.overview.charts;
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const chart = CHARTS[vnode.attrs.chart];
|
||||
const device = vnode.attrs["device"];
|
||||
const chart = CHARTS[vnode.attrs["chart"]];
|
||||
for (const slice of Object.values(chart.slices)) {
|
||||
const filter = slice.filter;
|
||||
const filter = slice["filter"];
|
||||
if (store.evaluateExpression(filter, device)) {
|
||||
const dot = m(
|
||||
"svg",
|
||||
@@ -26,13 +25,15 @@ export default function component() {
|
||||
cx: "0.5em",
|
||||
cy: "0.5em",
|
||||
r: "0.4em",
|
||||
fill: slice.color
|
||||
fill: slice["color"]
|
||||
})
|
||||
);
|
||||
return m("span.overview-dot", dot, `${slice.label}`);
|
||||
return m("span.overview-dot", dot, `${slice["label"]}`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,35 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { m } from "../components";
|
||||
|
||||
export default function component() {
|
||||
return {
|
||||
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
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||
import { m } from "../components";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
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
|
||||
);
|
||||
|
||||
return m(
|
||||
"tr",
|
||||
{
|
||||
onupdate: vn => {
|
||||
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||
? ""
|
||||
: "none";
|
||||
}
|
||||
},
|
||||
m("th", parameter["label"]),
|
||||
m("td", p)
|
||||
);
|
||||
});
|
||||
|
||||
return m("table.parameter-list", rows);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,23 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
import * as expressionParser from "../../lib/common/expression-parser";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
oninit: vnode => {
|
||||
const obj = vnode.attrs.parameter;
|
||||
if (!Array.isArray(obj) || !obj[0] === "PARAM")
|
||||
const obj = vnode.attrs["parameter"];
|
||||
if (!Array.isArray(obj) || obj[0] !== "PARAM")
|
||||
throw new Error("Object must be a parameter path");
|
||||
vnode.state.object = obj[1];
|
||||
vnode.state.parameters = Object.values(vnode.attrs.childParameters);
|
||||
vnode.state["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;
|
||||
const device = vnode.attrs["device"];
|
||||
const object = store.evaluateExpression(vnode.state["object"], device);
|
||||
const parameters = vnode.state["parameters"];
|
||||
|
||||
if (!device[object]) return null;
|
||||
|
||||
@@ -31,13 +30,14 @@ export default function component() {
|
||||
}
|
||||
}
|
||||
|
||||
const headers = Object.values(parameters).map(p => m("th", p.label));
|
||||
const headers = Object.values(parameters).map(p => m("th", p["label"]));
|
||||
|
||||
const thead = m("thead", m("tr", headers));
|
||||
|
||||
const rows = [];
|
||||
for (const i of instances) {
|
||||
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
|
||||
let filter =
|
||||
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
||||
|
||||
filter = expressionParser.map(filter, e => {
|
||||
if (Array.isArray(e) && e[0] === "PARAM")
|
||||
@@ -126,9 +126,11 @@ export default function component() {
|
||||
}
|
||||
|
||||
let label;
|
||||
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
|
||||
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
||||
|
||||
return [label, m("table.table", thead, m("tbody", rows))];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
@@ -31,14 +30,14 @@ const evaluateParam = memoize((exp, obj, now) => {
|
||||
return { value, timestamp, parameter };
|
||||
});
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
const { value, timestamp, parameter } = evaluateParam(
|
||||
vnode.attrs.parameter,
|
||||
vnode.attrs.device,
|
||||
vnode.attrs["parameter"],
|
||||
vnode.attrs["device"],
|
||||
store.getTimestamp()
|
||||
);
|
||||
|
||||
@@ -77,7 +76,7 @@ export default function component() {
|
||||
onmouseover: e => {
|
||||
e.redraw = false;
|
||||
// Don't update any child element
|
||||
if (e.target === el.dom) {
|
||||
if (e.target === (el as VnodeDOM).dom) {
|
||||
const now = Date.now();
|
||||
const localeString = new Date(timestamp).toLocaleString();
|
||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||
@@ -89,4 +88,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,56 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
const REFRESH_INTERVAL = 3000;
|
||||
|
||||
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 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";
|
||||
}
|
||||
|
||||
return m("div", `Pinging ${vnode.state.host}: ${status}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as store from "../store";
|
||||
|
||||
const REFRESH_INTERVAL = 3000;
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
oninit: vnode => {
|
||||
vnode.state["timestamp"] = 0;
|
||||
vnode.state["ping"] = null;
|
||||
vnode.state["host"] = null;
|
||||
vnode.state["timeout"] = null;
|
||||
},
|
||||
view: vnode => {
|
||||
const refresh = (): void => {
|
||||
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 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";
|
||||
}
|
||||
|
||||
return m("div", `Pinging ${vnode.state["host"]}: ${status}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,14 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as taskQueue from "../task-queue";
|
||||
import * as store from "../store";
|
||||
import * as notifications from "../notifications";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
|
||||
return m(
|
||||
"button.primary",
|
||||
@@ -16,9 +15,9 @@ export default function component() {
|
||||
title: "Initiate session and refresh basic parameters",
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const params = Object.values(vnode.attrs.parameters)
|
||||
const params = Object.values(vnode.attrs["parameters"])
|
||||
.map(exp => {
|
||||
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
|
||||
if (!Array.isArray(exp) || exp[0] !== "PARAM") return null;
|
||||
return store.evaluateExpression(exp[1], device);
|
||||
})
|
||||
.filter(exp => !!exp);
|
||||
@@ -68,4 +67,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,15 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "../components";
|
||||
import * as notifications from "../notifications";
|
||||
import * as store from "../store";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const device = vnode.attrs.device;
|
||||
const device = vnode.attrs["device"];
|
||||
let writable = true;
|
||||
if (vnode.attrs.writable != null) writable = vnode.attrs.writable;
|
||||
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
|
||||
|
||||
const tags = [];
|
||||
for (const p of Object.keys(device))
|
||||
@@ -87,4 +86,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,5 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
import { m } from "./components";
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
@@ -7,13 +5,18 @@ import putFormComponent from "./put-form-component";
|
||||
import * as overlay from "./overlay";
|
||||
import * as expressionParser from "../lib/common/expression-parser";
|
||||
import { loadCodeMirror } from "./dynamic-loader";
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
|
||||
const attributes = [
|
||||
{ id: "_id", label: "Key" },
|
||||
{ id: "value", label: "Value" }
|
||||
];
|
||||
|
||||
function putActionHandler(action, _object, isNew) {
|
||||
interface ValidationErrors {
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const object = Object.assign({}, _object);
|
||||
if (action === "save") {
|
||||
@@ -78,11 +81,11 @@ const formData = {
|
||||
attributes: attributes
|
||||
};
|
||||
|
||||
function escapeRegExp(str) {
|
||||
function escapeRegExp(str): string {
|
||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||
}
|
||||
|
||||
export function init() {
|
||||
export function init(): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("config", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -97,7 +100,7 @@ export function init() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(confsResponse, searchString) {
|
||||
function renderTable(confsResponse, searchString): Children {
|
||||
const confs = confsResponse.value.sort((a, b) => {
|
||||
return a._id < b._id ? -1 : 1;
|
||||
});
|
||||
@@ -113,14 +116,14 @@ function renderTable(confsResponse, searchString) {
|
||||
for (const conf of confs) {
|
||||
const attrs = {};
|
||||
if (regex && !regex.test(conf._id) && !regex.test(conf.value))
|
||||
attrs.style = "display: none;";
|
||||
attrs["style"] = "display: none;";
|
||||
|
||||
const edit = m(
|
||||
"button",
|
||||
{
|
||||
title: "Edit config value",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -130,9 +133,9 @@ function renderTable(confsResponse, searchString) {
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, false)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const ErrorList = errors ? Object.values(errors) : [];
|
||||
if (ErrorList.length) {
|
||||
for (const err of ErrorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -190,7 +193,7 @@ function renderTable(confsResponse, searchString) {
|
||||
return m("table", m("tbody", rows));
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Config - GenieACS";
|
||||
@@ -199,10 +202,10 @@ export function component() {
|
||||
type: "text",
|
||||
placeholder: "Search config",
|
||||
oninput: e => {
|
||||
vnode.state.searchString = e.target.value;
|
||||
vnode.state["searchString"] = e.target.value;
|
||||
e.redraw = false;
|
||||
clearTimeout(vnode.state.timeout);
|
||||
vnode.state.timeout = setTimeout(m.redraw, 250);
|
||||
clearTimeout(vnode.state["timeout"]);
|
||||
vnode.state["timeout"] = setTimeout(m.redraw, 250);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -213,7 +216,7 @@ export function component() {
|
||||
{
|
||||
title: "Create new config",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -222,9 +225,11 @@ export function component() {
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, true)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -259,11 +264,11 @@ export function component() {
|
||||
m(
|
||||
".parameter-list",
|
||||
{ style: "height: 400px" },
|
||||
renderTable(confs, vnode.state.searchString)
|
||||
renderTable(confs, vnode.state["searchString"])
|
||||
),
|
||||
m(".actions-bar", newConfig)
|
||||
)
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1 @@
|
||||
"use strict";
|
||||
|
||||
export default window.clientConfig;
|
||||
@@ -1,10 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import * as store from "./store";
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("devices", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -17,12 +16,12 @@ export function init(args) {
|
||||
});
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = `${vnode.attrs.deviceId} - Devices - GenieACS`;
|
||||
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
||||
|
||||
const dev = store.fetch("devices", vnode.attrs.deviceFilter).value;
|
||||
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
|
||||
if (!dev.length) return "Loading";
|
||||
const conf = config.ui.device;
|
||||
const cmps = [];
|
||||
@@ -30,7 +29,7 @@ export function component() {
|
||||
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"]), cmps];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -42,7 +41,7 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.authorizer.hasAccess("devices", 2))
|
||||
return void reject(new Error("You are not authorized to view this page"));
|
||||
@@ -63,7 +62,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const devices = devicesResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -195,7 +194,7 @@ function renderTable(
|
||||
]);
|
||||
}
|
||||
|
||||
function renderActions(selected) {
|
||||
function renderActions(selected): Children {
|
||||
const buttons = [];
|
||||
|
||||
buttons.push(
|
||||
@@ -336,65 +335,68 @@ function renderActions(selected) {
|
||||
return m(".actions-bar", buttons);
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Devices - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set("/devices", ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||
sort: sort
|
||||
});
|
||||
const count = store.count("devices", filter);
|
||||
|
||||
const selected = new Set();
|
||||
if (vnode.state.selected) {
|
||||
if (vnode.state["selected"]) {
|
||||
for (const d of devs.value) {
|
||||
if (vnode.state.selected.has(d["DeviceID.ID"].value[0]))
|
||||
if (vnode.state["selected"].has(d["DeviceID.ID"].value[0]))
|
||||
selected.add(d["DeviceID.ID"].value[0]);
|
||||
}
|
||||
}
|
||||
vnode.state.selected = selected;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(
|
||||
vnode.attrs.filter,
|
||||
vnode.attrs.indexParameters
|
||||
vnode.attrs["filter"],
|
||||
vnode.attrs["indexParameters"]
|
||||
);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "devices";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing devices"),
|
||||
m(filterComponent, {
|
||||
resource: "devices",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
devs,
|
||||
vnode.attrs.indexParameters,
|
||||
vnode.attrs["indexParameters"],
|
||||
count.value,
|
||||
selected,
|
||||
showMore,
|
||||
@@ -406,4 +408,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,21 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import m, {
|
||||
Children,
|
||||
Child,
|
||||
ClosureComponent,
|
||||
Component,
|
||||
VnodeDOM
|
||||
} from "mithril";
|
||||
import * as taskQueue from "./task-queue";
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
function mparam(param) {
|
||||
function mparam(param): Children {
|
||||
return m("span.parameter", { title: param }, `${param}`);
|
||||
}
|
||||
|
||||
function mval(val) {
|
||||
function mval(val): Children {
|
||||
return m("span.value", { title: val }, `${val}`);
|
||||
}
|
||||
|
||||
function renderStagingSpv(task, queueFunc, cancelFunc) {
|
||||
function keydown(e) {
|
||||
function renderStagingSpv(task, queueFunc, cancelFunc): Children {
|
||||
function keydown(e): void {
|
||||
if (e.keyCode === 13) queueFunc();
|
||||
else if (e.keyCode === 27) cancelFunc();
|
||||
else e.redraw = false;
|
||||
@@ -33,7 +36,7 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
||||
},
|
||||
onkeydown: keydown,
|
||||
oncreate: vnode => {
|
||||
vnode.dom.focus();
|
||||
(vnode.dom as HTMLSelectElement).focus();
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -55,11 +58,11 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
||||
},
|
||||
onkeydown: keydown,
|
||||
oncreate: vnode => {
|
||||
vnode.dom.focus();
|
||||
vnode.dom.select();
|
||||
(vnode.dom as HTMLInputElement).focus();
|
||||
(vnode.dom as HTMLInputElement).select();
|
||||
// Need to prevent scrolling on focus because
|
||||
// we're animating height and using overflow: hidden
|
||||
vnode.dom.parentNode.parentNode.scrollTop = 0;
|
||||
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -67,7 +70,7 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
||||
return [m("span", "Editing ", mparam(task.parameterValues[0][0])), input];
|
||||
}
|
||||
|
||||
function renderStagingDownload(task) {
|
||||
function renderStagingDownload(task): Children {
|
||||
task.invalid = !task.fileName || !task.fileType;
|
||||
const files = store.fetch("files", true);
|
||||
const idParts = task.device.split("-");
|
||||
@@ -138,22 +141,22 @@ function renderStagingDownload(task) {
|
||||
];
|
||||
}
|
||||
|
||||
function renderStaging(staging) {
|
||||
const elements = [];
|
||||
function renderStaging(staging): Child[] {
|
||||
const elements: Child[] = [];
|
||||
|
||||
for (const s of staging) {
|
||||
const queueFunc = () => {
|
||||
const queueFunc = (): void => {
|
||||
staging.delete(s);
|
||||
taskQueue.queueTask(s);
|
||||
};
|
||||
const cancelFunc = () => {
|
||||
const cancelFunc = (): void => {
|
||||
staging.delete(s);
|
||||
};
|
||||
|
||||
let elms;
|
||||
if (s.name === "setParameterValues")
|
||||
elms = renderStagingSpv(s, queueFunc, cancelFunc);
|
||||
else if (s.name === "download")
|
||||
elms = renderStagingDownload(s, queueFunc, cancelFunc);
|
||||
else if (s.name === "download") elms = renderStagingDownload(s);
|
||||
|
||||
const queue = m(
|
||||
"button.primary",
|
||||
@@ -171,9 +174,9 @@ function renderStaging(staging) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
function renderQueue(queue) {
|
||||
const details = [];
|
||||
const devices = {};
|
||||
function renderQueue(queue): Child[] {
|
||||
const details: Child[] = [];
|
||||
const devices: { [deviceId: string]: any[] } = {};
|
||||
for (const t of queue) {
|
||||
devices[t.device] = devices[t.device] || [];
|
||||
devices[t.device].push(t);
|
||||
@@ -282,8 +285,8 @@ function renderQueue(queue) {
|
||||
return details;
|
||||
}
|
||||
|
||||
function renderNotifications(notifs) {
|
||||
const notificationElements = [];
|
||||
function renderNotifications(notifs): Child[] {
|
||||
const notificationElements: Child[] = [];
|
||||
|
||||
for (const n of notifs) {
|
||||
notificationElements.push(
|
||||
@@ -293,10 +296,10 @@ function renderNotifications(notifs) {
|
||||
class: n.type,
|
||||
style: "position: absolute;opacity: 0",
|
||||
oncreate: vnode => {
|
||||
vnode.dom.style.opacity = 1;
|
||||
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
||||
},
|
||||
onbeforeremove: vnode => {
|
||||
vnode.dom.style.opacity = 0;
|
||||
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
@@ -312,7 +315,7 @@ function renderNotifications(notifs) {
|
||||
return notificationElements;
|
||||
}
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const queue = taskQueue.getQueue();
|
||||
@@ -324,21 +327,26 @@ export default function component() {
|
||||
const stagingElements = renderStaging(staging);
|
||||
const queueElements = renderQueue(queue);
|
||||
|
||||
function repositionNotifications() {
|
||||
function repositionNotifications(): void {
|
||||
let top = 10;
|
||||
for (const c of notificationElements) {
|
||||
c.dom.style.top = top;
|
||||
top += c.dom.offsetHeight + 10;
|
||||
for (const c of notificationElements as VnodeDOM[]) {
|
||||
(c.dom as HTMLDivElement).style.top = `${top}`;
|
||||
top += (c.dom as HTMLDivElement).offsetHeight + 10;
|
||||
}
|
||||
}
|
||||
|
||||
function resizeDrawer() {
|
||||
function resizeDrawer(): void {
|
||||
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 s of stagingElements as VnodeDOM[]) {
|
||||
height = Math.max(
|
||||
height,
|
||||
(s.dom as HTMLDivElement).offsetTop +
|
||||
(s.dom as HTMLDivElement).offsetHeight
|
||||
);
|
||||
}
|
||||
} else if (vnode.state["mouseIn"]) {
|
||||
for (const c of drawerElement.children)
|
||||
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
|
||||
}
|
||||
@@ -347,7 +355,7 @@ export default function component() {
|
||||
|
||||
if (stagingElements.length + queueElements.length) {
|
||||
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
|
||||
for (const t of queue) statusCount[t.status] += 1;
|
||||
for (const t of queue) statusCount[t["status"]] += 1;
|
||||
|
||||
const actions = m(
|
||||
".actions",
|
||||
@@ -358,7 +366,7 @@ export default function component() {
|
||||
disabled: !statusCount.queued,
|
||||
onclick: () => {
|
||||
const tasks = Array.from(taskQueue.getQueue()).filter(
|
||||
t => t.status === "queued"
|
||||
t => t["status"] === "queued"
|
||||
);
|
||||
taskQueue
|
||||
.commit(
|
||||
@@ -451,25 +459,26 @@ export default function component() {
|
||||
key: "drawer",
|
||||
style: "opacity: 0;height: 0;",
|
||||
oncreate: vnode2 => {
|
||||
vnode.state.mouseIn = false;
|
||||
vnode2.dom.style.opacity = 1;
|
||||
vnode.state["mouseIn"] = false;
|
||||
(vnode2.dom as HTMLDivElement).style.opacity = "1";
|
||||
resizeDrawer();
|
||||
},
|
||||
onmouseover: e => {
|
||||
vnode.state.mouseIn = true;
|
||||
vnode.state["mouseIn"] = true;
|
||||
resizeDrawer();
|
||||
e.redraw = false;
|
||||
},
|
||||
onmouseleave: e => {
|
||||
vnode.state.mouseIn = false;
|
||||
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;
|
||||
(vnode2.dom as HTMLDivElement).onmouseover = null;
|
||||
(vnode2.dom as HTMLDivElement).onmouseleave = null;
|
||||
(vnode2.dom as HTMLDivElement).style.opacity = "0";
|
||||
(vnode2.dom as HTMLDivElement).style.height = "0";
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
@@ -496,4 +505,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,9 +1,7 @@
|
||||
"use strict";
|
||||
export let codeMirror;
|
||||
export let yaml;
|
||||
|
||||
let codeMirror;
|
||||
let yaml;
|
||||
|
||||
function loadCodeMirror() {
|
||||
export function loadCodeMirror(): Promise<void> {
|
||||
if (codeMirror) return Promise.resolve();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -20,7 +18,7 @@ function loadCodeMirror() {
|
||||
});
|
||||
}
|
||||
|
||||
function loadYaml() {
|
||||
export function loadYaml(): Promise<void> {
|
||||
if (yaml) return Promise.resolve();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -32,5 +30,3 @@ function loadYaml() {
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
export { loadCodeMirror, codeMirror, loadYaml, yaml };
|
||||
@@ -1,12 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { m } from "./components";
|
||||
|
||||
export function component() {
|
||||
return {
|
||||
view: function(vnode) {
|
||||
document.title = "Error! - GenieACS";
|
||||
return m("p.error", vnode.attrs.error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "./components";
|
||||
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: function(vnode) {
|
||||
document.title = "Error! - GenieACS";
|
||||
return m("p.error", vnode.attrs["error"]);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -46,7 +45,7 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("faults", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -72,7 +71,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const faults = faultsResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -229,57 +228,60 @@ function renderTable(
|
||||
];
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Faults - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set("/faults", ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||
sort: sort
|
||||
});
|
||||
const count = store.count("faults", filter);
|
||||
|
||||
const selected = new Set();
|
||||
if (vnode.state.selected) {
|
||||
if (vnode.state["selected"]) {
|
||||
for (const f of faults.value)
|
||||
if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]);
|
||||
if (vnode.state["selected"].has(f["_id"])) selected.add(f["_id"]);
|
||||
}
|
||||
vnode.state.selected = selected;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "faults";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing faults"),
|
||||
m(filterComponent, {
|
||||
resource: "faults",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
faults,
|
||||
count.value,
|
||||
@@ -292,4 +294,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -16,7 +15,12 @@ const PAGE_SIZE = config.ui.pageSize || 10;
|
||||
const memoizedParse = memoize(parse);
|
||||
const memoizedJsonParse = memoize(JSON.parse);
|
||||
|
||||
const attributes = [
|
||||
const attributes: {
|
||||
id: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
options?: any;
|
||||
}[] = [
|
||||
{ id: "_id", label: "Name" },
|
||||
{
|
||||
id: "metadata.fileType",
|
||||
@@ -50,7 +54,11 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
function putActionHandler(action, _object) {
|
||||
interface ValidationErrors {
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
function putActionHandler(action, _object): Promise<ValidationErrors> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const object = Object.assign({}, _object);
|
||||
if (action === "save") {
|
||||
@@ -109,7 +117,7 @@ const getDownloadUrl = memoize(filter => {
|
||||
})}`;
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("files", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -129,7 +137,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const files = filesResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -270,7 +278,7 @@ function renderTable(
|
||||
{
|
||||
title: "Create new file",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -279,9 +287,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -318,58 +328,63 @@ function renderTable(
|
||||
];
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Files - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set(m.route.get(), ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
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"]);
|
||||
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;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "files";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing files"),
|
||||
m(filterComponent, {
|
||||
resource: "files",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
files,
|
||||
count.value,
|
||||
@@ -382,4 +397,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import m, { ClosureComponent, Component } from "mithril";
|
||||
import { parse, stringify, map } from "../lib/common/expression-parser";
|
||||
import memoize from "../lib/common/memoize";
|
||||
import Autocomplete from "./autocomplete-compnent";
|
||||
import * as smartQuery from "./smart-query";
|
||||
import { filterToMongoQuery } from "../lib/ui/mongodb-functions";
|
||||
import { Expression } from "../lib/types";
|
||||
|
||||
const getAutocomplete = memoize(resource => {
|
||||
const labels = smartQuery.getLabels(resource);
|
||||
@@ -16,7 +15,7 @@ const getAutocomplete = memoize(resource => {
|
||||
return autocomplete;
|
||||
});
|
||||
|
||||
function parseFilter(resource, f) {
|
||||
function parseFilter(resource, f): Expression {
|
||||
let exp;
|
||||
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
||||
const k = f.split(":", 1)[0];
|
||||
@@ -38,7 +37,7 @@ function parseFilter(resource, f) {
|
||||
return exp;
|
||||
}
|
||||
|
||||
function stringifyFilter(f) {
|
||||
function stringifyFilter(f: Expression): string {
|
||||
if (Array.isArray(f) && f[0] === "FUNC" && f[1] === "Q")
|
||||
return `${f[2]}: ${f[3]}`;
|
||||
return stringify(f);
|
||||
@@ -56,39 +55,39 @@ const splitFilter = memoize(filter => {
|
||||
return list;
|
||||
});
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
if (
|
||||
!vnode.state.filterList ||
|
||||
vnode.attrs.filter !== vnode.state.filter
|
||||
!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);
|
||||
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) => {
|
||||
function onChange(): void {
|
||||
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);
|
||||
return parseFilter(vnode.attrs["resource"], f);
|
||||
} catch (err) {
|
||||
vnode.state.filterInvalid |= 1 << idx;
|
||||
vnode.state["filterInvalid"] |= 1 << idx;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
vnode.state.filterList.push("");
|
||||
vnode.state["filterList"].push("");
|
||||
|
||||
if (!vnode.state.filterInvalid) {
|
||||
delete vnode.state.filter;
|
||||
if (!vnode.state["filterInvalid"]) {
|
||||
delete vnode.state["filter"];
|
||||
if (filter.length === 0) {
|
||||
vnode.attrs.onChange("");
|
||||
vnode.attrs["onChange"]("");
|
||||
} else {
|
||||
if (filter.length > 1) filter = ["AND"].concat(filter);
|
||||
else filter = filter[0];
|
||||
vnode.attrs.onChange(stringify(filter));
|
||||
vnode.attrs["onChange"](stringify(filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,18 +95,20 @@ export default function component() {
|
||||
return m(
|
||||
"div.filter",
|
||||
[m("b", "Filter")].concat(
|
||||
vnode.state.filterList.map((fltr, idx) => {
|
||||
vnode.state["filterList"].map((fltr, idx) => {
|
||||
return m("input", {
|
||||
type: "text",
|
||||
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
|
||||
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();
|
||||
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);
|
||||
getAutocomplete(vnode.attrs["resource"]).attach(vn.dom);
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -115,4 +116,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,6 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import m, { ClosureComponent, Component } from "mithril";
|
||||
import menu from "./menu";
|
||||
import drawerComponent from "./drawer-component";
|
||||
import userMenu from "./user-menu";
|
||||
@@ -15,29 +13,39 @@ const adminPages = [
|
||||
"config"
|
||||
];
|
||||
|
||||
export default function layout() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
let sideMenu, group;
|
||||
if (adminPages.includes(vnode.attrs.page)) {
|
||||
|
||||
if (adminPages.includes(vnode.attrs["page"])) {
|
||||
group = "admin";
|
||||
sideMenu = m(adminMenu, { page: vnode.attrs.page });
|
||||
const attrs = {};
|
||||
attrs["page"] = vnode.attrs["page"];
|
||||
sideMenu = m(adminMenu, attrs);
|
||||
}
|
||||
|
||||
const attrs = {};
|
||||
attrs["page"] = group || vnode.attrs["page"];
|
||||
|
||||
return [
|
||||
m("#header", [
|
||||
m("img.logo", { src: "logo.svg" }),
|
||||
m(userMenu),
|
||||
m(menu, { page: group || vnode.attrs.page }),
|
||||
m(menu, attrs),
|
||||
m(drawerComponent)
|
||||
]),
|
||||
m(
|
||||
"#content-wrapper",
|
||||
sideMenu,
|
||||
m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
|
||||
m("#content", { class: `page-${vnode.attrs["page"]}` }, [
|
||||
vnode.children
|
||||
])
|
||||
),
|
||||
overlay.render()
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,17 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "./components";
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
return Promise.resolve(args);
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
if (window.username) m.route.set(vnode.attrs.continue || "/");
|
||||
if (window.username) m.route.set(vnode.attrs["continue"] || "/");
|
||||
|
||||
document.title = "Login - GenieACS";
|
||||
return [
|
||||
@@ -24,11 +23,11 @@ export function component() {
|
||||
m("input", {
|
||||
name: "username",
|
||||
type: "text",
|
||||
value: vnode.state.username,
|
||||
value: vnode.state["username"],
|
||||
oncreate: vnode2 => {
|
||||
vnode2.dom.focus();
|
||||
(vnode2.dom as HTMLInputElement).focus();
|
||||
},
|
||||
oninput: m.withAttr("value", v => (vnode.state.username = v))
|
||||
oninput: m.withAttr("value", v => (vnode.state["username"] = v))
|
||||
})
|
||||
),
|
||||
m(
|
||||
@@ -37,8 +36,8 @@ export function component() {
|
||||
m("input", {
|
||||
name: "password",
|
||||
type: "password",
|
||||
value: vnode.state.password,
|
||||
oninput: m.withAttr("value", v => (vnode.state.password = v))
|
||||
value: vnode.state["password"],
|
||||
oninput: m.withAttr("value", v => (vnode.state["password"] = v))
|
||||
})
|
||||
),
|
||||
m(
|
||||
@@ -50,7 +49,7 @@ export function component() {
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
store
|
||||
.logIn(vnode.state.username, vnode.state.password)
|
||||
.logIn(vnode.state["username"], vnode.state["password"])
|
||||
.then(() => {
|
||||
location.reload();
|
||||
})
|
||||
@@ -68,4 +67,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,15 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import m, { ClosureComponent, Component } from "mithril";
|
||||
import * as overlay from "./overlay";
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const text = vnode.attrs.text;
|
||||
const element = vnode.attrs.element || "span";
|
||||
const text = vnode.attrs["text"];
|
||||
const element = vnode.attrs["element"] || "span";
|
||||
|
||||
function overflowed(_vnode) {
|
||||
function overflowed(_vnode): void {
|
||||
if (_vnode.dom.classList.contains("long-text-overflowed")) return;
|
||||
_vnode.dom.classList.add("long-text-overflowed");
|
||||
_vnode.dom.onclick = e => {
|
||||
@@ -20,8 +18,8 @@ export default function component() {
|
||||
rows: 24,
|
||||
readonly: "",
|
||||
oncreate: vnode2 => {
|
||||
vnode2.dom.focus();
|
||||
vnode2.dom.select();
|
||||
(vnode2.dom as HTMLTextAreaElement).focus();
|
||||
(vnode2.dom as HTMLTextAreaElement).select();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -40,8 +38,10 @@ export default function component() {
|
||||
},
|
||||
onupdate: vnode2 => {
|
||||
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
|
||||
vnode2.dom.classList.remove("long-text-overflowed");
|
||||
vnode2.dom.onclick = null;
|
||||
(vnode2.dom as HTMLElement).classList.remove(
|
||||
"long-text-overflowed"
|
||||
);
|
||||
(vnode2.dom as HTMLElement).onclick = null;
|
||||
} else {
|
||||
overflowed(vnode2);
|
||||
}
|
||||
@@ -53,4 +53,6 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,11 +1,9 @@
|
||||
"use strict";
|
||||
import m, { ClosureComponent, Component } from "mithril";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
export default function menu() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
const active = { [vnode.attrs.page]: "active" };
|
||||
const active = { [vnode.attrs["page"]]: "active" };
|
||||
|
||||
const tabs = [];
|
||||
if (window.authorizer.hasAccess("devices", 1)) {
|
||||
@@ -60,4 +58,6 @@ export default function menu() {
|
||||
return m("nav", m("ul", tabs));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,10 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
const notifications = new Set();
|
||||
interface Notification {
|
||||
type: string;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export function push(type, message) {
|
||||
const notifications = new Set<Notification>();
|
||||
|
||||
export function push(type, message): void {
|
||||
const n = { type: type, message: message, timestamp: Date.now() };
|
||||
notifications.add(n);
|
||||
m.redraw();
|
||||
@@ -14,6 +18,6 @@ export function push(type, message) {
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
export function getNotifications() {
|
||||
export function getNotifications(): Set<Notification> {
|
||||
return notifications;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
"use strict";
|
||||
import m, { Children } from "mithril";
|
||||
|
||||
import m from "mithril";
|
||||
type OverlayCallback = () => Children;
|
||||
|
||||
let overlayCallback = null;
|
||||
let overlayCallback: OverlayCallback = null;
|
||||
|
||||
function open(callback) {
|
||||
export function open(callback: OverlayCallback): void {
|
||||
overlayCallback = callback;
|
||||
}
|
||||
|
||||
function close(callback) {
|
||||
export function close(callback: OverlayCallback): boolean {
|
||||
if (callback === overlayCallback) {
|
||||
overlayCallback = null;
|
||||
return true;
|
||||
@@ -17,7 +17,7 @@ function close(callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function render() {
|
||||
export function render(): Children {
|
||||
if (overlayCallback) {
|
||||
return m(
|
||||
".overlay-wrapper",
|
||||
@@ -28,11 +28,11 @@ function render() {
|
||||
},
|
||||
style: "opacity: 0",
|
||||
oncreate: vnode => {
|
||||
vnode.dom.focus();
|
||||
vnode.dom.style.opacity = 1;
|
||||
(vnode.dom as HTMLDivElement).focus();
|
||||
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
||||
},
|
||||
onbeforeremove: vnode => {
|
||||
vnode.dom.style.opacity = 0;
|
||||
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
@@ -62,5 +62,3 @@ document.addEventListener("keydown", e => {
|
||||
window.addEventListener("popstate", () => {
|
||||
if (close(overlayCallback)) m.redraw();
|
||||
});
|
||||
|
||||
export { open, close, render };
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import * as store from "./store";
|
||||
@@ -8,25 +7,25 @@ import pieChartComponent from "./pie-chart-component";
|
||||
const GROUPS = config.ui.overview.groups;
|
||||
const CHARTS = {};
|
||||
for (const group of Object.values(GROUPS)) {
|
||||
for (const chartName of Object.values(group["charts"]))
|
||||
for (const chartName of Object.values(group["charts"]) as string[])
|
||||
CHARTS[chartName] = config.ui.overview.charts[chartName];
|
||||
}
|
||||
|
||||
function queryCharts(charts) {
|
||||
function queryCharts(charts): {} {
|
||||
charts = Object.assign({}, charts);
|
||||
for (let [chartName, chart] of Object.entries(charts)) {
|
||||
charts[chartName] = chart = Object.assign({}, chart);
|
||||
chart.slices = Object.assign({}, chart.slices);
|
||||
for (let [sliceName, slice] of Object.entries(chart.slices)) {
|
||||
const filter = slice.filter;
|
||||
chart.slices[sliceName] = slice = Object.assign({}, slice);
|
||||
slice.count = store.count("devices", filter);
|
||||
chart["slices"] = Object.assign({}, chart["slices"]);
|
||||
for (let [sliceName, slice] of Object.entries(chart["slices"])) {
|
||||
const filter = slice["filter"];
|
||||
chart["slices"][sliceName] = slice = Object.assign({}, slice);
|
||||
slice["count"] = store.count("devices", filter);
|
||||
}
|
||||
}
|
||||
return charts;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
export function init(): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("devices", 1)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -36,21 +35,23 @@ export function init() {
|
||||
return Promise.resolve({ charts: queryCharts(CHARTS) });
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): 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));
|
||||
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];
|
||||
for (const chartName of Object.values(group["charts"]) as string[]) {
|
||||
const chart = vnode.attrs["charts"][chartName];
|
||||
const chartChildren = [];
|
||||
if (chart.label) chartChildren.push(m("h2", chart.label));
|
||||
|
||||
chartChildren.push(m(pieChartComponent, { chart: chart }));
|
||||
const attrs = {};
|
||||
attrs["chart"] = chart;
|
||||
chartChildren.push(m(pieChartComponent, attrs));
|
||||
|
||||
groupChildren.push(m(".overview-chart", chartChildren));
|
||||
}
|
||||
@@ -61,4 +62,4 @@ export function component() {
|
||||
return children;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,15 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import { stringify } from "../lib/common/expression-parser";
|
||||
import memoize from "../lib/common/memoize";
|
||||
|
||||
const memoizedStringify = memoize(stringify);
|
||||
|
||||
const drawChart = function(chartData) {
|
||||
function drawChart(chartData): Children {
|
||||
const slices = chartData.slices;
|
||||
const total = Array.from(Object.values(chartData.slices)).reduce(
|
||||
(a, s) => a + (s.count.value || 0),
|
||||
const total: number = Array.from(Object.values(chartData.slices)).reduce(
|
||||
(a: number, s) => a + (s["count"]["value"] || 0),
|
||||
0
|
||||
);
|
||||
const legend = [];
|
||||
@@ -21,21 +20,21 @@ const drawChart = function(chartData) {
|
||||
let endX, endY;
|
||||
|
||||
for (const slice of Object.values(slices)) {
|
||||
const percent = total > 0 ? (slice.count.value || 0) / total : 0;
|
||||
const percent = total > 0 ? (slice["count"]["value"] || 0) / total : 0;
|
||||
legend.push(
|
||||
m(".legend-line", [
|
||||
m("span.color", {
|
||||
style: `background-color: ${slice.color} !important;`
|
||||
style: `background-color: ${slice["color"]} !important;`
|
||||
}),
|
||||
`${slice.label}: `,
|
||||
`${slice["label"]}: `,
|
||||
m(
|
||||
"a",
|
||||
{
|
||||
href: `/#!/devices/?${m.buildQueryString({
|
||||
filter: memoizedStringify(slice.filter)
|
||||
filter: memoizedStringify(slice["filter"])
|
||||
})}`
|
||||
},
|
||||
slice.count.value || 0
|
||||
slice["count"]["value"] || 0
|
||||
),
|
||||
` (${(percent * 100).toFixed(2)}%)`
|
||||
])
|
||||
@@ -58,7 +57,7 @@ const drawChart = function(chartData) {
|
||||
paths.push(
|
||||
m("path", {
|
||||
d: sketch,
|
||||
fill: slice.color
|
||||
fill: slice["color"]
|
||||
})
|
||||
);
|
||||
|
||||
@@ -72,7 +71,7 @@ const drawChart = function(chartData) {
|
||||
"a",
|
||||
{
|
||||
"xlink:href": `/#!/devices/?${m.buildQueryString({
|
||||
filter: memoizedStringify(slice.filter)
|
||||
filter: memoizedStringify(slice["filter"])
|
||||
})}`
|
||||
},
|
||||
[
|
||||
@@ -113,12 +112,14 @@ const drawChart = function(chartData) {
|
||||
),
|
||||
m(".legend", legend)
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
return drawChart(vnode.attrs.chart);
|
||||
return drawChart(vnode.attrs["chart"]);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -35,7 +34,11 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
function putActionHandler(action, _object, isNew) {
|
||||
interface ValidationErrors {
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const object = Object.assign({}, _object);
|
||||
if (action === "save") {
|
||||
@@ -45,7 +48,7 @@ function putActionHandler(action, _object, isNew) {
|
||||
const errors = {};
|
||||
|
||||
if (!id) errors["_id"] = "ID can not be empty";
|
||||
if (!object.provision) errors.provision = "Provision not selected";
|
||||
if (!object.provision) errors["provision"] = "Provision not selected";
|
||||
|
||||
if (Object.keys(errors).length) return void resolve(errors);
|
||||
|
||||
@@ -107,7 +110,7 @@ const getDownloadUrl = memoize(filter => {
|
||||
})}`;
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("presets", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -127,7 +130,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const presets = presetsResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -215,7 +218,7 @@ function renderTable(
|
||||
"a",
|
||||
{
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -225,9 +228,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, false)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -334,7 +339,7 @@ function renderTable(
|
||||
{
|
||||
title: "Create new preset",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -343,9 +348,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, true)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -382,37 +389,39 @@ function renderTable(
|
||||
];
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Presets - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set(m.route.get(), ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||
sort: sort
|
||||
});
|
||||
const count = store.count("presets", filter);
|
||||
@@ -435,27 +444,28 @@ export function component() {
|
||||
|
||||
for (const p of provisions.value) provisionIds.add(p["_id"]);
|
||||
|
||||
provisionAttr.options = Array.from(provisionIds);
|
||||
provisionAttr["options"] = Array.from(provisionIds);
|
||||
}
|
||||
|
||||
const selected = new Set();
|
||||
if (vnode.state.selected) {
|
||||
if (vnode.state["selected"]) {
|
||||
for (const preset of presets.value) {
|
||||
if (vnode.state.selected.has(preset["_id"]))
|
||||
if (vnode.state["selected"].has(preset["_id"]))
|
||||
selected.add(preset["_id"]);
|
||||
}
|
||||
}
|
||||
vnode.state.selected = selected;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "presets";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing presets"),
|
||||
m(filterComponent, {
|
||||
resource: "presets",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
presets,
|
||||
count.value,
|
||||
@@ -468,4 +478,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -30,7 +29,11 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
function putActionHandler(action, _object, isNew) {
|
||||
interface ValidationErrors {
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const object = Object.assign({}, _object);
|
||||
if (action === "save") {
|
||||
@@ -97,7 +100,7 @@ const getDownloadUrl = memoize(filter => {
|
||||
})}`;
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("provisions", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -124,7 +127,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const provisions = provisionsResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -197,7 +200,7 @@ function renderTable(
|
||||
"a",
|
||||
{
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -207,9 +210,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, false)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -322,7 +327,7 @@ function renderTable(
|
||||
{
|
||||
title: "Create new provision",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -331,9 +336,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, true)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -370,60 +377,63 @@ function renderTable(
|
||||
];
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Provisions - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set(m.route.get(), ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
const count = store.count("provisions", filter);
|
||||
|
||||
const selected = new Set();
|
||||
if (vnode.state.selected) {
|
||||
if (vnode.state["selected"]) {
|
||||
for (const provision of provisions.value) {
|
||||
if (vnode.state.selected.has(provision["_id"]))
|
||||
if (vnode.state["selected"].has(provision["_id"]))
|
||||
selected.add(provision["_id"]);
|
||||
}
|
||||
}
|
||||
vnode.state.selected = selected;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "provisions";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing provisions"),
|
||||
m(filterComponent, {
|
||||
resource: "provisions",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
provisions,
|
||||
count.value,
|
||||
@@ -436,4 +446,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { VnodeDOM, ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import { codeMirror } from "./dynamic-loader";
|
||||
|
||||
@@ -10,7 +9,7 @@ const singular = {
|
||||
files: "file"
|
||||
};
|
||||
|
||||
function createField(current, attr, focus) {
|
||||
function createField(current, attr, focus): Children {
|
||||
if (attr.type === "combo") {
|
||||
const options = [m("option", { value: "" }, "--Please choose--")];
|
||||
for (const op of attr.options) options.push(m("option", { value: op }, op));
|
||||
@@ -25,7 +24,7 @@ function createField(current, attr, focus) {
|
||||
value: selected,
|
||||
oncreate: focus
|
||||
? _vnode => {
|
||||
_vnode.dom.focus();
|
||||
(_vnode.dom as HTMLSelectElement).focus();
|
||||
}
|
||||
: null,
|
||||
onchange: e => {
|
||||
@@ -45,10 +44,14 @@ function createField(current, attr, focus) {
|
||||
lineNumbers: true,
|
||||
extraKeys: {
|
||||
"Ctrl-Enter": () => {
|
||||
_vnode.dom.form.querySelector("button[type=submit]").click();
|
||||
((_vnode.dom as HTMLTextAreaElement).form.querySelector(
|
||||
"button[type=submit]"
|
||||
) as HTMLButtonElement).click();
|
||||
},
|
||||
"Cmd-Enter": () => {
|
||||
_vnode.dom.form.querySelector("button[type=submit]").click();
|
||||
((_vnode.dom as HTMLTextAreaElement).form.querySelector(
|
||||
"button[type=submit]"
|
||||
) as HTMLButtonElement).click();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -66,7 +69,7 @@ function createField(current, attr, focus) {
|
||||
name: attr.id,
|
||||
oncreate: focus
|
||||
? _vnode => {
|
||||
_vnode.dom.focus();
|
||||
(_vnode.dom as HTMLInputElement).focus();
|
||||
}
|
||||
: null,
|
||||
onchange: e => {
|
||||
@@ -83,7 +86,7 @@ function createField(current, attr, focus) {
|
||||
value: current.object[attr.id],
|
||||
oncreate: focus
|
||||
? _vnode => {
|
||||
_vnode.dom.focus();
|
||||
(_vnode.dom as HTMLInputElement).focus();
|
||||
}
|
||||
: null,
|
||||
oninput: e => {
|
||||
@@ -93,21 +96,21 @@ function createField(current, attr, focus) {
|
||||
});
|
||||
}
|
||||
|
||||
export default function component() {
|
||||
const component: ClosureComponent = (): 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 = {
|
||||
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;
|
||||
@@ -126,27 +129,29 @@ export default function component() {
|
||||
);
|
||||
}
|
||||
|
||||
const submit = m("button.primary", { type: "submit" }, "Save");
|
||||
const submit = m(
|
||||
"button.primary",
|
||||
{ type: "submit" },
|
||||
"Save"
|
||||
) as VnodeDOM;
|
||||
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"
|
||||
)
|
||||
);
|
||||
buttons.push(m(
|
||||
"button.primary",
|
||||
{
|
||||
type: "button",
|
||||
title: `Delete ${singular[resource] || resource}`,
|
||||
onclick: e => {
|
||||
e.redraw = false;
|
||||
e.target.disabled = true;
|
||||
actionHandler("delete", current.object).then(() => {
|
||||
e.target.disabled = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
"Delete"
|
||||
) as VnodeDOM);
|
||||
}
|
||||
|
||||
form.push(m(".actions-bar", buttons));
|
||||
@@ -165,12 +170,12 @@ export default function component() {
|
||||
// const onsubmit = e.target.onsubmit;
|
||||
e.preventDefault();
|
||||
// e.target.onsubmit = null;
|
||||
submit.dom.disabled = true;
|
||||
(submit.dom as HTMLFormElement).disabled = true;
|
||||
// submit.dom.textContent = "Loading ...";
|
||||
actionHandler("save", current.object).then(() => {
|
||||
// submit.dom.textContent = "Save";
|
||||
// e.target.onsubmit = onsubmit;
|
||||
submit.dom.disabled = false;
|
||||
(submit.dom as HTMLFormElement).disabled = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -181,4 +186,6 @@ export default function component() {
|
||||
return m("div.put-form", children);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
import config from "./config";
|
||||
import { Expression } from "../lib/types";
|
||||
|
||||
const resources = {
|
||||
devices: {},
|
||||
@@ -34,19 +33,23 @@ const resources = {
|
||||
}
|
||||
};
|
||||
|
||||
for (const v of Object.values(config.ui.filters)) {
|
||||
for (const v of Object.values(config.ui.filters as {
|
||||
label: string;
|
||||
parameter: string;
|
||||
type: string;
|
||||
}[])) {
|
||||
resources.devices[v.label] = {
|
||||
parameter: v.parameter,
|
||||
type: (v.type || "").split(",").map(s => s.trim())
|
||||
};
|
||||
}
|
||||
|
||||
function getLabels(resource) {
|
||||
export function getLabels(resource): string[] {
|
||||
if (!resources[resource]) return [];
|
||||
return Object.keys(resources[resource]);
|
||||
}
|
||||
|
||||
function queryNumber(param, value) {
|
||||
function queryNumber(param, value): Expression {
|
||||
let op = "=";
|
||||
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
||||
if (value.startsWith(o)) {
|
||||
@@ -62,7 +65,7 @@ function queryNumber(param, value) {
|
||||
return [op, param, v];
|
||||
}
|
||||
|
||||
function queryTimestamp(param, value) {
|
||||
function queryTimestamp(param, value): Expression {
|
||||
let op = "=";
|
||||
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
||||
if (value.startsWith(o)) {
|
||||
@@ -78,11 +81,11 @@ function queryTimestamp(param, value) {
|
||||
return [op, param, v];
|
||||
}
|
||||
|
||||
function queryString(param, value) {
|
||||
function queryString(param, value): Expression {
|
||||
return ["LIKE", ["FUNC", "LOWER", param], value.toLowerCase()];
|
||||
}
|
||||
|
||||
function queryMac(param, value) {
|
||||
function queryMac(param, value): Expression {
|
||||
value = value.replace(/[^a-f0-9]/gi, "").toLowerCase();
|
||||
if (!value) return null;
|
||||
if (value.length === 12) {
|
||||
@@ -104,11 +107,11 @@ function queryMac(param, value) {
|
||||
];
|
||||
}
|
||||
|
||||
function unpack(resource, label, value) {
|
||||
export function unpack(resource, label, value): Expression {
|
||||
if (!resources[resource]) return null;
|
||||
const type = resources[resource][label].type;
|
||||
value = value.trim();
|
||||
const res = ["OR"];
|
||||
const res: Expression = ["OR"];
|
||||
|
||||
if (type.length === 0 || type.includes("number")) {
|
||||
const q = queryNumber(resources[resource][label].parameter, value);
|
||||
@@ -134,5 +137,3 @@ function unpack(resource, label, value) {
|
||||
else if (res.length === 2) return res[1];
|
||||
else return res;
|
||||
}
|
||||
|
||||
export { getLabels, unpack };
|
||||
+79
-74
@@ -1,9 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
import { map, stringify } from "../lib/common/expression-parser";
|
||||
import { or, and, not, evaluate, subset } from "../lib/common/expression";
|
||||
import memoize from "../lib/common/memoize";
|
||||
import { QueryOptions, Expression } from "../lib/types";
|
||||
|
||||
const memoizedStringify = memoize(stringify);
|
||||
const memoizedEvaluate = memoize(evaluate);
|
||||
@@ -21,7 +20,16 @@ const queries = {
|
||||
value: new WeakMap()
|
||||
};
|
||||
|
||||
const resources = {};
|
||||
interface Resources {
|
||||
[resource: string]: {
|
||||
objects: Map<string, any>;
|
||||
count: Map<string, QueryResponse>;
|
||||
fetch: Map<string, QueryResponse>;
|
||||
combinedFilter: Expression;
|
||||
};
|
||||
}
|
||||
|
||||
const resources: Resources = {};
|
||||
for (const r of [
|
||||
"devices",
|
||||
"faults",
|
||||
@@ -41,24 +49,29 @@ for (const r of [
|
||||
}
|
||||
|
||||
class QueryResponse {
|
||||
get fulfilled() {
|
||||
public get fulfilled(): boolean {
|
||||
queries.accessed.set(this, Date.now());
|
||||
return !!queries.fulfilled.get(this);
|
||||
}
|
||||
|
||||
get value() {
|
||||
public get fulfilling(): boolean {
|
||||
queries.accessed.set(this, Date.now());
|
||||
return queries.fulfilling.has(this);
|
||||
}
|
||||
|
||||
public get value(): any {
|
||||
queries.accessed.set(this, Date.now());
|
||||
return queries.value.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
function unpackExpression(exp) {
|
||||
export function unpackExpression(exp): Expression {
|
||||
if (!Array.isArray(exp)) return exp;
|
||||
const e = memoizedEvaluate(exp, null, fulfillTimestamp);
|
||||
return e;
|
||||
}
|
||||
|
||||
function count(resourceType, filter) {
|
||||
export function count(resourceType, filter): QueryResponse {
|
||||
const filterStr = memoizedStringify(filter);
|
||||
let queryResponse = resources[resourceType].count.get(filterStr);
|
||||
if (queryResponse) return queryResponse;
|
||||
@@ -70,48 +83,53 @@ function count(resourceType, filter) {
|
||||
return queryResponse;
|
||||
}
|
||||
|
||||
function limitFilter(filter, sort, bookmark) {
|
||||
const sortSort = (a, b) => Math.abs(b[1]) - Math.abs(a[1]);
|
||||
function limitFilter(filter, sort, bookmark): Expression {
|
||||
const sortSort = (a, b): number => Math.abs(b[1]) - Math.abs(a[1]);
|
||||
const arr = Object.entries(sort)
|
||||
.sort(sortSort)
|
||||
.reverse();
|
||||
return and(
|
||||
filter,
|
||||
arr.reduce((cur, kv) => {
|
||||
const [param, asc] = kv;
|
||||
if (asc <= 0) {
|
||||
if (bookmark[param] == null) {
|
||||
return or(
|
||||
["IS NOT NULL", ["PARAM", param]],
|
||||
and(["IS NULL", ["PARAM", param]], cur)
|
||||
);
|
||||
arr.reduce(
|
||||
(cur, kv) => {
|
||||
const [param, asc] = kv;
|
||||
if (asc <= 0) {
|
||||
if (bookmark[param] == null) {
|
||||
return or(
|
||||
["IS NOT NULL", ["PARAM", param]],
|
||||
and(["IS NULL", ["PARAM", param]], cur)
|
||||
);
|
||||
}
|
||||
|
||||
let f = null;
|
||||
|
||||
if (typeof bookmark[param] !== "string")
|
||||
f = or(f, [">=", ["PARAM", param], ""]);
|
||||
|
||||
f = or(f, [">", ["PARAM", param], bookmark[param]]);
|
||||
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
|
||||
} else {
|
||||
let f: Expression = ["IS NULL", ["PARAM", param]];
|
||||
if (bookmark[param] == null) return and(f, cur);
|
||||
|
||||
if (typeof bookmark[param] !== "number") {
|
||||
f = or(f, [">=", ["PARAM", param], 0]);
|
||||
f = or(f, ["<", ["PARAM", param], 0]);
|
||||
}
|
||||
|
||||
f = or(f, ["<", ["PARAM", param], bookmark[param]]);
|
||||
|
||||
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
|
||||
}
|
||||
|
||||
let f = null;
|
||||
|
||||
if (typeof bookmark[param] !== "string")
|
||||
f = or(f, [">=", ["PARAM", param], ""]);
|
||||
|
||||
f = or(f, [">", ["PARAM", param], bookmark[param]]);
|
||||
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
|
||||
} else {
|
||||
let f = ["IS NULL", ["PARAM", param]];
|
||||
if (bookmark[param] == null) return and(f, cur);
|
||||
|
||||
if (typeof bookmark[param] !== "number") {
|
||||
f = or(f, [">=", ["PARAM", param], 0]);
|
||||
f = or(f, ["<", ["PARAM", param], 0]);
|
||||
}
|
||||
|
||||
f = or(f, ["<", ["PARAM", param], bookmark[param]]);
|
||||
|
||||
return or(f, and(["=", ["PARAM", param], bookmark[param]], cur));
|
||||
}
|
||||
}, true)
|
||||
},
|
||||
true as Expression
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function compareFunction(sort) {
|
||||
function compareFunction(sort: {
|
||||
[param: string]: number;
|
||||
}): (a: any, b: any) => number {
|
||||
const sortEntries = Object.entries(sort).sort(
|
||||
(a, b) => Math.abs(b[1]) - Math.abs(a[1])
|
||||
);
|
||||
@@ -149,7 +167,7 @@ function compareFunction(sort) {
|
||||
};
|
||||
}
|
||||
|
||||
function findMatches(resourceType, filter, sort, limit) {
|
||||
function findMatches(resourceType, filter, sort, limit): any[] {
|
||||
// Handle "tag =" and "tag <>" special cases
|
||||
if (resourceType === "devices") {
|
||||
filter = map(filter, e => {
|
||||
@@ -176,7 +194,7 @@ function findMatches(resourceType, filter, sort, limit) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function inferQuery(resourceType, queryResponse) {
|
||||
function inferQuery(resourceType, queryResponse): void {
|
||||
const limit = queries.limit.get(queryResponse);
|
||||
let filter = queries.filter.get(queryResponse);
|
||||
filter = unpackExpression(filter);
|
||||
@@ -197,7 +215,11 @@ function inferQuery(resourceType, queryResponse) {
|
||||
);
|
||||
}
|
||||
|
||||
function fetch(resourceType, filter, options = {}) {
|
||||
export function fetch(
|
||||
resourceType,
|
||||
filter,
|
||||
options: QueryOptions = {}
|
||||
): QueryResponse {
|
||||
const filterStr = memoizedStringify(filter);
|
||||
const sort = Object.assign({}, options.sort);
|
||||
for (const [k, v] of Object.entries(sort)) sort[k] += Math.sign(v);
|
||||
@@ -220,7 +242,7 @@ function fetch(resourceType, filter, options = {}) {
|
||||
return queryResponse;
|
||||
}
|
||||
|
||||
function fulfill(accessTimestamp, _fulfillTimestamp) {
|
||||
export function fulfill(accessTimestamp, _fulfillTimestamp): Promise<boolean> {
|
||||
let updated = false;
|
||||
|
||||
if (_fulfillTimestamp > fulfillTimestamp) {
|
||||
@@ -270,7 +292,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
|
||||
}
|
||||
}
|
||||
|
||||
const toFetchAll = {};
|
||||
const toFetchAll: { [resourceType: string]: QueryResponse[] } = {};
|
||||
|
||||
for (const [resourceType, resource] of Object.entries(resources)) {
|
||||
for (const [queryResponseKey, queryResponse] of resource.fetch) {
|
||||
@@ -306,7 +328,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (res.length) {
|
||||
if ((res as {}[]).length) {
|
||||
// Generate bookmark object
|
||||
const bm = Object.keys(sort).reduce((b, k) => {
|
||||
if (res[0][k] != null) {
|
||||
@@ -395,7 +417,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
for (const r of res) {
|
||||
for (const r of res as {}[]) {
|
||||
const id =
|
||||
resourceType === "devices"
|
||||
? r["DeviceID.ID"].value[0]
|
||||
@@ -439,11 +461,11 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
|
||||
});
|
||||
}
|
||||
|
||||
function getTimestamp() {
|
||||
export function getTimestamp(): number {
|
||||
return fulfillTimestamp;
|
||||
}
|
||||
|
||||
function postTasks(deviceId, tasks) {
|
||||
export function postTasks(deviceId, tasks): Promise<string> {
|
||||
for (const t of tasks) {
|
||||
t.status = "pending";
|
||||
t.device = deviceId;
|
||||
@@ -471,7 +493,7 @@ function postTasks(deviceId, tasks) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateTags(deviceId, tags) {
|
||||
export function updateTags(deviceId, tags): Promise<void> {
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: `/api/devices/${encodeURIComponent(deviceId)}/tags`,
|
||||
@@ -479,14 +501,14 @@ function updateTags(deviceId, tags) {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteResource(resourceType, id) {
|
||||
export function deleteResource(resourceType, id): Promise<void> {
|
||||
return m.request({
|
||||
method: "DELETE",
|
||||
url: `/api/${resourceType}/${encodeURIComponent(id)}`
|
||||
});
|
||||
}
|
||||
|
||||
function putResource(resourceType, id, object) {
|
||||
export function putResource(resourceType, id, object): Promise<void> {
|
||||
for (const k in object) if (object[k] === undefined) object[k] = null;
|
||||
|
||||
return m.request({
|
||||
@@ -496,7 +518,7 @@ function putResource(resourceType, id, object) {
|
||||
});
|
||||
}
|
||||
|
||||
function resourceExists(resource, id) {
|
||||
export function resourceExists(resource, id): Promise<number> {
|
||||
const param = resource === "devices" ? "DeviceID.ID" : "_id";
|
||||
const filter = ["=", ["PARAM", param], id];
|
||||
return m.request({
|
||||
@@ -511,12 +533,12 @@ function resourceExists(resource, id) {
|
||||
});
|
||||
}
|
||||
|
||||
function evaluateExpression(exp, obj) {
|
||||
export function evaluateExpression(exp, obj): Expression {
|
||||
if (!Array.isArray(exp)) return exp;
|
||||
return memoizedEvaluate(exp, obj, fulfillTimestamp);
|
||||
}
|
||||
|
||||
function logIn(username, password) {
|
||||
export function logIn(username, password): Promise<void> {
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: "/login",
|
||||
@@ -525,32 +547,15 @@ function logIn(username, password) {
|
||||
});
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
export function logOut(): Promise<void> {
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: "/logout"
|
||||
});
|
||||
}
|
||||
|
||||
function ping(host) {
|
||||
export function ping(host): Promise<{}> {
|
||||
return m.request({
|
||||
url: `/api/ping/${encodeURIComponent(host)}`
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
count,
|
||||
fetch,
|
||||
fulfill,
|
||||
unpackExpression,
|
||||
getTimestamp,
|
||||
postTasks,
|
||||
updateTags,
|
||||
deleteResource,
|
||||
putResource,
|
||||
resourceExists,
|
||||
evaluateExpression,
|
||||
logIn,
|
||||
logOut,
|
||||
ping
|
||||
};
|
||||
@@ -1,47 +1,45 @@
|
||||
"use strict";
|
||||
|
||||
import * as store from "./store";
|
||||
|
||||
const queue = new Set();
|
||||
const staging = new Set();
|
||||
|
||||
function queueTask(task) {
|
||||
export function queueTask(task): void {
|
||||
staging.delete(task);
|
||||
task.status = "queued";
|
||||
queue.add(task);
|
||||
}
|
||||
|
||||
function deleteTask(task) {
|
||||
export function deleteTask(task): void {
|
||||
staging.delete(task);
|
||||
queue.delete(task);
|
||||
}
|
||||
|
||||
function getQueue() {
|
||||
export function getQueue(): Set<{}> {
|
||||
return queue;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
export function clear(): void {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
function getStaging() {
|
||||
export function getStaging(): Set<{}> {
|
||||
return staging;
|
||||
}
|
||||
|
||||
function clearStaging() {
|
||||
export function clearStaging(): void {
|
||||
staging.clear();
|
||||
}
|
||||
|
||||
function stageSpv(task) {
|
||||
export function stageSpv(task): void {
|
||||
staging.add(task);
|
||||
}
|
||||
|
||||
function stageDownload(task) {
|
||||
export function stageDownload(task): void {
|
||||
staging.add(task);
|
||||
}
|
||||
|
||||
function commit(tasks, callback) {
|
||||
const devices = {};
|
||||
export function commit(tasks, callback): Promise<void> {
|
||||
const devices: { [deviceId: string]: any[] } = {};
|
||||
for (const t of tasks) {
|
||||
devices[t.device] = devices[t.device] || [];
|
||||
devices[t.device].push(t);
|
||||
@@ -72,15 +70,3 @@ function commit(tasks, callback) {
|
||||
if (--counter === 0) resolve();
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
queueTask,
|
||||
deleteTask,
|
||||
clear,
|
||||
getQueue,
|
||||
getStaging,
|
||||
clearStaging,
|
||||
stageSpv,
|
||||
stageDownload,
|
||||
commit
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
const UNITS = {
|
||||
year: 12 * 30 * 24 * 60 * 60 * 1000,
|
||||
month: 30 * 24 * 60 * 60 * 1000,
|
||||
@@ -9,7 +7,7 @@ const UNITS = {
|
||||
second: 1000
|
||||
};
|
||||
|
||||
function timeAgo(dtime) {
|
||||
export default function timeAgo(dtime): string {
|
||||
let res = "";
|
||||
let level = 2;
|
||||
|
||||
@@ -30,5 +28,3 @@ function timeAgo(dtime) {
|
||||
|
||||
return res + "ago";
|
||||
}
|
||||
|
||||
export default timeAgo;
|
||||
@@ -1,11 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import m, { ClosureComponent, Component } from "mithril";
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
export default function userMenu() {
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: () => {
|
||||
if (window.username) {
|
||||
@@ -48,4 +45,6 @@ export default function userMenu() {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default component;
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
|
||||
import { ClosureComponent, Component, Children } from "mithril";
|
||||
import { m } from "./components";
|
||||
import config from "./config";
|
||||
import filterComponent from "./filter-component";
|
||||
@@ -30,7 +29,11 @@ const unpackSmartQuery = memoize(query => {
|
||||
});
|
||||
});
|
||||
|
||||
function putActionHandler(action, _object, isNew) {
|
||||
interface ValidationErrors {
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const object = Object.assign({}, _object);
|
||||
if (action === "save") {
|
||||
@@ -97,7 +100,7 @@ const getDownloadUrl = memoize(filter => {
|
||||
})}`;
|
||||
});
|
||||
|
||||
export function init(args) {
|
||||
export function init(args): Promise<{}> {
|
||||
if (!window.authorizer.hasAccess("virtualParameters", 2)) {
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
@@ -124,7 +127,7 @@ function renderTable(
|
||||
downloadUrl,
|
||||
sort,
|
||||
onSortChange
|
||||
) {
|
||||
): Children {
|
||||
const virtualParameters = virtualParametersResponse.value;
|
||||
const selectAll = m("input", {
|
||||
type: "checkbox",
|
||||
@@ -198,7 +201,7 @@ function renderTable(
|
||||
"a",
|
||||
{
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -208,9 +211,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, false)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -332,7 +337,7 @@ function renderTable(
|
||||
{
|
||||
title: "Create new virtual parameter",
|
||||
onclick: () => {
|
||||
const cb = () => {
|
||||
const cb = (): Children => {
|
||||
return m(
|
||||
putFormComponent,
|
||||
Object.assign(
|
||||
@@ -341,9 +346,11 @@ function renderTable(
|
||||
return new Promise(resolve => {
|
||||
putActionHandler(action, object, true)
|
||||
.then(errors => {
|
||||
errors = errors ? Object.values(errors) : [];
|
||||
if (errors.length) {
|
||||
for (const err of errors)
|
||||
const errorList = errors
|
||||
? Object.values(errors)
|
||||
: [];
|
||||
if (errorList.length) {
|
||||
for (const err of errorList)
|
||||
notifications.push("error", err);
|
||||
} else {
|
||||
overlay.close(cb);
|
||||
@@ -380,60 +387,63 @@ function renderTable(
|
||||
];
|
||||
}
|
||||
|
||||
export function component() {
|
||||
export const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
view: vnode => {
|
||||
document.title = "Virtual Parameters - GenieACS";
|
||||
|
||||
function showMore() {
|
||||
vnode.state.showCount =
|
||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
||||
function showMore(): void {
|
||||
vnode.state["showCount"] =
|
||||
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
function onFilterChanged(filter) {
|
||||
function onFilterChanged(filter): void {
|
||||
const ops = { filter };
|
||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
||||
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||
m.route.set(m.route.get(), ops);
|
||||
}
|
||||
|
||||
function onSortChange(sort) {
|
||||
function onSortChange(sort): void {
|
||||
const ops = { sort };
|
||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
||||
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)
|
||||
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,
|
||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
const count = store.count("virtualParameters", filter);
|
||||
|
||||
const selected = new Set();
|
||||
if (vnode.state.selected) {
|
||||
if (vnode.state["selected"]) {
|
||||
for (const virtualParameter of virtualParameters.value) {
|
||||
if (vnode.state.selected.has(virtualParameter["_id"]))
|
||||
if (vnode.state["selected"].has(virtualParameter["_id"]))
|
||||
selected.add(virtualParameter["_id"]);
|
||||
}
|
||||
}
|
||||
vnode.state.selected = selected;
|
||||
vnode.state["selected"] = selected;
|
||||
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
||||
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||
|
||||
const attrs = {};
|
||||
attrs["resource"] = "virtualParameters";
|
||||
attrs["filter"] = vnode.attrs["filter"];
|
||||
attrs["onChange"] = onFilterChanged;
|
||||
|
||||
return [
|
||||
m("h1", "Listing virtual parameters"),
|
||||
m(filterComponent, {
|
||||
resource: "virtualParameters",
|
||||
filter: vnode.attrs.filter,
|
||||
onChange: onFilterChanged
|
||||
}),
|
||||
m(filterComponent, attrs),
|
||||
renderTable(
|
||||
virtualParameters,
|
||||
count.value,
|
||||
@@ -446,4 +456,4 @@ export function component() {
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user