Update ESLint and Prettier

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

No files matched your search

+15 -26
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -31,8 +29,8 @@ import { version as VERSION } from "../package.json";
logger.init("cwmp", VERSION); logger.init("cwmp", VERSION);
const SERVICE_ADDRESS = config.get("CWMP_INTERFACE"); const SERVICE_ADDRESS = config.get("CWMP_INTERFACE") as string;
const SERVICE_PORT = config.get("CWMP_PORT"); const SERVICE_PORT = config.get("CWMP_PORT") as number;
function exitWorkerGracefully(): void { function exitWorkerGracefully(): void {
setTimeout(exitWorkerUngracefully, 5000).unref(); setTimeout(exitWorkerUngracefully, 5000).unref();
@@ -40,7 +38,7 @@ function exitWorkerGracefully(): void {
db.disconnect(), db.disconnect(),
cache.disconnect(), cache.disconnect(),
extensions.killAll(), extensions.killAll(),
cluster.worker.disconnect() cluster.worker.disconnect(),
]).catch(exitWorkerUngracefully); ]).catch(exitWorkerUngracefully);
} }
@@ -51,12 +49,12 @@ function exitWorkerUngracefully(): void {
} }
if (!cluster.worker) { if (!cluster.worker) {
const WORKER_COUNT = config.get("CWMP_WORKER_PROCESSES"); const WORKER_COUNT = config.get("CWMP_WORKER_PROCESSES") as number;
logger.info({ logger.info({
message: `genieacs-cwmp starting`, message: `genieacs-cwmp starting`,
pid: process.pid, pid: process.pid,
version: VERSION version: VERSION,
}); });
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS); cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
@@ -64,7 +62,7 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
logger.info({ logger.info({
message: "Received signal SIGINT, exiting", message: "Received signal SIGINT, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
@@ -73,28 +71,25 @@ if (!cluster.worker) {
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
logger.info({ logger.info({
message: "Received signal SIGTERM, exiting", message: "Received signal SIGTERM, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
}); });
} else { } else {
const ssl = { const ssl = {
key: config.get("CWMP_SSL_KEY"), key: config.get("CWMP_SSL_KEY") as string,
cert: config.get("CWMP_SSL_CERT") cert: config.get("CWMP_SSL_CERT") as string,
}; };
process.on("uncaughtException", err => { process.on("uncaughtException", (err) => {
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return; if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
logger.error({ logger.error({
message: "Uncaught exception", message: "Uncaught exception",
exception: err, exception: err,
pid: process.pid pid: process.pid,
}); });
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
const initPromise = Promise.all([db.connect(), cache.connect()]) const initPromise = Promise.all([db.connect(), cache.connect()])
@@ -108,7 +103,7 @@ if (!cluster.worker) {
0 0
); );
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throw err; throw err;
}); });
@@ -116,19 +111,13 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
} }
+9 -9
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -35,8 +33,8 @@ function errorToFault(err: Error): Fault {
message: err.message, message: err.message,
detail: { detail: {
name: err.name, name: err.name,
message: err.message message: err.message,
} },
}; };
if (err.stack) { if (err.stack) {
@@ -56,16 +54,16 @@ function errorToFault(err: Error): Fault {
return fault; return fault;
} }
process.on("uncaughtException", err => { process.on("uncaughtException", (err) => {
const fault = errorToFault(err); const fault = errorToFault(err);
jobs.forEach(jobId => { jobs.forEach((jobId) => {
process.send([jobId, fault, null]); process.send([jobId, fault, null]);
}); });
jobs.clear(); jobs.clear();
process.disconnect(); process.disconnect();
}); });
process.on("message", message => { process.on("message", (message) => {
jobs.add(message[0]); jobs.add(message[0]);
if (!script) { if (!script) {
@@ -79,7 +77,7 @@ process.on("message", message => {
if (!script[funcName]) { if (!script[funcName]) {
const fault = { const fault = {
code: "ext", code: "ext",
message: `No such function '${funcName}' in extension '${fileName}'` message: `No such function '${funcName}' in extension '${fileName}'`,
}; };
process.send([message[0], fault, null]); process.send([message[0], fault, null]);
return; return;
@@ -93,4 +91,6 @@ process.on("message", message => {
}); });
// Ignore SIGINT // Ignore SIGINT
process.on("SIGINT", () => {}); process.on("SIGINT", () => {
// Ignore
});
+15 -26
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -30,15 +28,15 @@ import { version as VERSION } from "../package.json";
logger.init("fs", VERSION); logger.init("fs", VERSION);
const SERVICE_ADDRESS = config.get("FS_INTERFACE"); const SERVICE_ADDRESS = config.get("FS_INTERFACE") as string;
const SERVICE_PORT = config.get("FS_PORT"); const SERVICE_PORT = config.get("FS_PORT") as number;
function exitWorkerGracefully(): void { function exitWorkerGracefully(): void {
setTimeout(exitWorkerUngracefully, 5000).unref(); setTimeout(exitWorkerUngracefully, 5000).unref();
Promise.all([ Promise.all([
db.disconnect(), db.disconnect(),
cache.disconnect(), cache.disconnect(),
cluster.worker.disconnect() cluster.worker.disconnect(),
]).catch(exitWorkerUngracefully); ]).catch(exitWorkerUngracefully);
} }
@@ -47,12 +45,12 @@ function exitWorkerUngracefully(): void {
} }
if (!cluster.worker) { if (!cluster.worker) {
const WORKER_COUNT = config.get("FS_WORKER_PROCESSES"); const WORKER_COUNT = config.get("FS_WORKER_PROCESSES") as number;
logger.info({ logger.info({
message: `genieacs-fs starting`, message: `genieacs-fs starting`,
pid: process.pid, pid: process.pid,
version: VERSION version: VERSION,
}); });
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS); cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
@@ -60,7 +58,7 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
logger.info({ logger.info({
message: "Received signal SIGINT, exiting", message: "Received signal SIGINT, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
@@ -69,31 +67,28 @@ if (!cluster.worker) {
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
logger.info({ logger.info({
message: "Received signal SIGTERM, exiting", message: "Received signal SIGTERM, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
}); });
} else { } else {
const ssl = { const ssl = {
key: config.get("FS_SSL_KEY"), key: config.get("FS_SSL_KEY") as string,
cert: config.get("FS_SSL_CERT") cert: config.get("FS_SSL_CERT") as string,
}; };
let stopping = false; let stopping = false;
process.on("uncaughtException", err => { process.on("uncaughtException", (err) => {
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return; if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
logger.error({ logger.error({
message: "Uncaught exception", message: "Uncaught exception",
exception: err, exception: err,
pid: process.pid pid: process.pid,
}); });
stopping = true; stopping = true;
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
const _listener = (req, res): void => { const _listener = (req, res): void => {
@@ -105,7 +100,7 @@ if (!cluster.worker) {
.then(() => { .then(() => {
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener); server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throw err; throw err;
}); });
@@ -114,20 +109,14 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
} }
+15 -26
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -31,8 +29,8 @@ import { version as VERSION } from "../package.json";
logger.init("nbi", VERSION); logger.init("nbi", VERSION);
const SERVICE_ADDRESS = config.get("NBI_INTERFACE"); const SERVICE_ADDRESS = config.get("NBI_INTERFACE") as string;
const SERVICE_PORT = config.get("NBI_PORT"); const SERVICE_PORT = config.get("NBI_PORT") as number;
function exitWorkerGracefully(): void { function exitWorkerGracefully(): void {
setTimeout(exitWorkerUngracefully, 5000).unref(); setTimeout(exitWorkerUngracefully, 5000).unref();
@@ -40,7 +38,7 @@ function exitWorkerGracefully(): void {
db.disconnect(), db.disconnect(),
cache.disconnect(), cache.disconnect(),
extensions.killAll(), extensions.killAll(),
cluster.worker.disconnect() cluster.worker.disconnect(),
]).catch(exitWorkerUngracefully); ]).catch(exitWorkerUngracefully);
} }
@@ -51,12 +49,12 @@ function exitWorkerUngracefully(): void {
} }
if (!cluster.worker) { if (!cluster.worker) {
const WORKER_COUNT = config.get("NBI_WORKER_PROCESSES"); const WORKER_COUNT = config.get("NBI_WORKER_PROCESSES") as number;
logger.info({ logger.info({
message: `genieacs-nbi starting`, message: `genieacs-nbi starting`,
pid: process.pid, pid: process.pid,
version: VERSION version: VERSION,
}); });
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS); cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
@@ -64,7 +62,7 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
logger.info({ logger.info({
message: "Received signal SIGINT, exiting", message: "Received signal SIGINT, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
@@ -73,31 +71,28 @@ if (!cluster.worker) {
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
logger.info({ logger.info({
message: "Received signal SIGTERM, exiting", message: "Received signal SIGTERM, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
}); });
} else { } else {
const ssl = { const ssl = {
key: config.get("NBI_SSL_KEY"), key: config.get("NBI_SSL_KEY") as string,
cert: config.get("NBI_SSL_CERT") cert: config.get("NBI_SSL_CERT") as string,
}; };
let stopping = false; let stopping = false;
process.on("uncaughtException", err => { process.on("uncaughtException", (err) => {
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return; if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
logger.error({ logger.error({
message: "Uncaught exception", message: "Uncaught exception",
exception: err, exception: err,
pid: process.pid pid: process.pid,
}); });
stopping = true; stopping = true;
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
const _listener = (req, res): void => { const _listener = (req, res): void => {
@@ -109,7 +104,7 @@ if (!cluster.worker) {
.then(() => { .then(() => {
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener); server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throw err; throw err;
}); });
@@ -118,20 +113,14 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
} }
+15 -26
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -32,8 +30,8 @@ import { version as VERSION } from "../package.json";
logger.init("ui", VERSION); logger.init("ui", VERSION);
const SERVICE_ADDRESS = config.get("UI_INTERFACE"); const SERVICE_ADDRESS = config.get("UI_INTERFACE") as string;
const SERVICE_PORT = config.get("UI_PORT"); const SERVICE_PORT = config.get("UI_PORT") as number;
function exitWorkerGracefully(): void { function exitWorkerGracefully(): void {
setTimeout(exitWorkerUngracefully, 5000).unref(); setTimeout(exitWorkerUngracefully, 5000).unref();
@@ -42,7 +40,7 @@ function exitWorkerGracefully(): void {
db2.disconnect(), db2.disconnect(),
cache.disconnect(), cache.disconnect(),
extensions.killAll(), extensions.killAll(),
cluster.worker.disconnect() cluster.worker.disconnect(),
]).catch(exitWorkerUngracefully); ]).catch(exitWorkerUngracefully);
} }
@@ -53,12 +51,12 @@ function exitWorkerUngracefully(): void {
} }
if (!cluster.worker) { if (!cluster.worker) {
const WORKER_COUNT = config.get("UI_WORKER_PROCESSES"); const WORKER_COUNT = config.get("UI_WORKER_PROCESSES") as number;
logger.info({ logger.info({
message: `genieacs-ui starting`, message: `genieacs-ui starting`,
pid: process.pid, pid: process.pid,
version: VERSION version: VERSION,
}); });
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS); cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
@@ -66,7 +64,7 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
logger.info({ logger.info({
message: "Received signal SIGINT, exiting", message: "Received signal SIGINT, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
@@ -75,31 +73,28 @@ if (!cluster.worker) {
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
logger.info({ logger.info({
message: "Received signal SIGTERM, exiting", message: "Received signal SIGTERM, exiting",
pid: process.pid pid: process.pid,
}); });
cluster.stop(); cluster.stop();
}); });
} else { } else {
const ssl = { const ssl = {
key: config.get("UI_SSL_KEY"), key: config.get("UI_SSL_KEY") as string,
cert: config.get("UI_SSL_CERT") cert: config.get("UI_SSL_CERT") as string,
}; };
let stopping = false; let stopping = false;
process.on("uncaughtException", err => { process.on("uncaughtException", (err) => {
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return; if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
logger.error({ logger.error({
message: "Uncaught exception", message: "Uncaught exception",
exception: err, exception: err,
pid: process.pid pid: process.pid,
}); });
stopping = true; stopping = true;
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
const _listener = (req, res): void => { const _listener = (req, res): void => {
@@ -111,7 +106,7 @@ if (!cluster.worker) {
.then(() => { .then(() => {
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener); server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throw err; throw err;
}); });
@@ -120,20 +115,14 @@ if (!cluster.worker) {
process.on("SIGINT", () => { process.on("SIGINT", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
stopping = true; stopping = true;
initPromise.finally(() => { initPromise.finally(() => {
server server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
.stop()
.then(exitWorkerGracefully)
.catch(exitWorkerUngracefully);
}); });
}); });
} }
+38 -47
View File
@@ -21,7 +21,6 @@ import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { promisify } from "util"; import { promisify } from "util";
import { rollup, WarningHandler } from "rollup"; import { rollup, WarningHandler } from "rollup";
import rollupReplace from "rollup-plugin-replace";
import rollupJson from "rollup-plugin-json"; import rollupJson from "rollup-plugin-json";
import typescript from "rollup-plugin-typescript"; import typescript from "rollup-plugin-typescript";
import { terser } from "rollup-plugin-terser"; import { terser } from "rollup-plugin-terser";
@@ -78,7 +77,7 @@ const externals = [
"codemirror", "codemirror",
"codemirror/mode/javascript/javascript", "codemirror/mode/javascript/javascript",
"codemirror/mode/yaml/yaml", "codemirror/mode/yaml/yaml",
"ipaddr.js" "ipaddr.js",
]; ];
function rmDirSync(dirPath): void { function rmDirSync(dirPath): void {
@@ -123,9 +122,7 @@ function generateSymbol(id: string, svgStr: string): string {
} }
} }
const symbolBody = xml.children[0].children const symbolBody = xml.children[0].children
.map(c => { .map((c) => xmlTostring(c))
return xmlTostring(c);
})
.join(""); .join("");
return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`; return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`;
} }
@@ -147,7 +144,7 @@ async function init(): Promise<void> {
delete packageJson["devDependencies"]; delete packageJson["devDependencies"];
packageJson["scripts"] = { packageJson["scripts"] = {
install: packageJson["scripts"].install, install: packageJson["scripts"].install,
configure: packageJson["scripts"].configure configure: packageJson["scripts"].configure,
}; };
packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`; packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`;
fs.writeFileSync( fs.writeFileSync(
@@ -173,7 +170,7 @@ async function copyStatic(): Promise<void> {
"README.md", "README.md",
"CHANGELOG.md", "CHANGELOG.md",
"public/logo.svg", "public/logo.svg",
"public/favicon.png" "public/favicon.png",
]; ];
for (const file of files) { for (const file of files) {
@@ -194,35 +191,31 @@ async function generateCss(): Promise<void> {
stage: 3, stage: 3,
features: { features: {
"nesting-rules": true, "nesting-rules": true,
"color-mod-function": true "color-mod-function": true,
} },
}), }),
cssnano cssnano,
]).process(cssIn, { from: cssInPath, to: cssOutPath }); ]).process(cssIn, { from: cssInPath, to: cssOutPath });
fs.writeFileSync(cssOutPath, cssOut.css); fs.writeFileSync(cssOutPath, cssOut.css);
} }
async function generateToolsJs(): Promise<void> { async function generateToolsJs(): Promise<void> {
for (const bin of ["dump-data-model"]) { for (const bin of ["dump-data-model"]) {
const inputFile = path.resolve(INPUT_DIR, `tools/${bin}`); const inputFile = path.resolve(INPUT_DIR, `tools/${bin}.ts`);
const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`); const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`);
const bundle = await rollup({ const bundle = await rollup({
input: inputFile, input: inputFile,
external: externals, external: externals,
acorn: { acorn: {
allowHashBang: true allowHashBang: true,
}, },
plugins: [ plugins: [
rollupReplace({
delimiters: ["", ""],
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
}),
typescript({ typescript({
tsconfig: "./tsconfig.json", tsconfig: "./tsconfig.json",
include: [`tools/${bin}`, "lib/**/*.ts"] include: [`tools/${bin}.ts`, "lib/**/*.ts"],
}), }),
MODE === "production" ? terser() : null MODE === "production" ? terser() : null,
] ],
}); });
await bundle.write({ await bundle.write({
@@ -231,7 +224,7 @@ async function generateToolsJs(): Promise<void> {
sourcemap: "inline", sourcemap: "inline",
sourcemapExcludeSources: true, sourcemapExcludeSources: true,
banner: "#!/usr/bin/env node", banner: "#!/usr/bin/env node",
file: outputFile file: outputFile,
}); });
// Mark as executable // Mark as executable
@@ -246,25 +239,21 @@ async function generateBackendJs(): Promise<void> {
"genieacs-ext", "genieacs-ext",
"genieacs-nbi", "genieacs-nbi",
"genieacs-fs", "genieacs-fs",
"genieacs-ui" "genieacs-ui",
]) { ]) {
const inputFile = path.resolve(INPUT_DIR, `bin/${bin}`); const inputFile = path.resolve(INPUT_DIR, `bin/${bin}.ts`);
const outputFile = path.resolve(OUTPUT_DIR, `bin/${bin}`); const outputFile = path.resolve(OUTPUT_DIR, `bin/${bin}`);
const bundle = await rollup({ const bundle = await rollup({
input: inputFile, input: inputFile,
external: externals, external: externals,
acorn: { acorn: {
allowHashBang: true allowHashBang: true,
}, },
treeshake: { treeshake: {
propertyReadSideEffects: false, propertyReadSideEffects: false,
pureExternalModules: true pureExternalModules: true,
}, },
plugins: [ plugins: [
rollupReplace({
delimiters: ["", ""],
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
}),
rollupJson({ preferConst: true }), rollupJson({ preferConst: true }),
{ {
resolveId: (importee, importer) => { resolveId: (importee, importer) => {
@@ -274,14 +263,14 @@ async function generateBackendJs(): Promise<void> {
return path.resolve(OUTPUT_DIR, "package.json"); return path.resolve(OUTPUT_DIR, "package.json");
} }
return null; return null;
} },
}, },
typescript({ typescript({
tsconfig: "./tsconfig.json", tsconfig: "./tsconfig.json",
include: [`bin/${bin}`, "lib/**/*.ts"] include: [`bin/${bin}.ts`, "lib/**/*.ts"],
}), }),
MODE === "production" ? terser() : null MODE === "production" ? terser() : null,
] ],
}); });
await bundle.write({ await bundle.write({
@@ -290,7 +279,7 @@ async function generateBackendJs(): Promise<void> {
sourcemap: "inline", sourcemap: "inline",
sourcemapExcludeSources: true, sourcemapExcludeSources: true,
banner: "#!/usr/bin/env node", banner: "#!/usr/bin/env node",
file: outputFile file: outputFile,
}); });
// Mark as executable // Mark as executable
@@ -308,17 +297,17 @@ async function generateFrontendJs(): Promise<void> {
external: externals, external: externals,
plugins: [ plugins: [
rollupJson({ preferConst: true }), rollupJson({ preferConst: true }),
typescript({ tsconfig: "./tsconfig.json" }) typescript({ tsconfig: "./tsconfig.json" }),
], ],
inlineDynamicImports: true, inlineDynamicImports: true,
treeshake: { treeshake: {
propertyReadSideEffects: false, propertyReadSideEffects: false,
pureExternalModules: true pureExternalModules: true,
}, },
onwarn: ((warning, warn) => { onwarn: ((warning, warn) => {
// Ignore circular dependency warnings // Ignore circular dependency warnings
if (warning.code !== "CIRCULAR_DEPENDENCY") warn(warning); if (warning.code !== "CIRCULAR_DEPENDENCY") warn(warning);
}) as WarningHandler }) as WarningHandler,
}); });
await bundle.write({ await bundle.write({
@@ -326,14 +315,14 @@ async function generateFrontendJs(): Promise<void> {
format: "esm", format: "esm",
sourcemap: "inline", sourcemap: "inline",
sourcemapExcludeSources: true, sourcemapExcludeSources: true,
file: outputFile file: outputFile,
}); });
const webpackConf = { const webpackConf = {
mode: MODE, mode: MODE,
entry: outputFile, entry: outputFile,
resolve: { resolve: {
aliasFields: ["module"] aliasFields: ["module"],
}, },
devtool: "nosources-source-map", devtool: "nosources-source-map",
module: { module: {
@@ -341,14 +330,14 @@ async function generateFrontendJs(): Promise<void> {
{ {
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ["source-map-loader"],
enforce: "pre" enforce: "pre",
} },
] ],
}, },
output: { output: {
path: path.resolve(OUTPUT_DIR, "public"), path: path.resolve(OUTPUT_DIR, "public"),
filename: "app.js" filename: "app.js",
} },
}; };
const stats = await promisify(webpack)(webpackConf); const stats = await promisify(webpack)(webpackConf);
@@ -379,13 +368,15 @@ init()
generateIconsSprite(), generateIconsSprite(),
generateToolsJs(), generateToolsJs(),
generateBackendJs(), generateBackendJs(),
generateFrontendJs() generateFrontendJs(),
]) ])
.then(() => {}) .then(() => {
.catch(err => { // Ignore
})
.catch((err) => {
process.stderr.write(err.stack + "\n"); process.stderr.write(err.stack + "\n");
}); });
}) })
.catch(err => { .catch((err) => {
process.stderr.write(err.stack + "\n"); process.stderr.write(err.stack + "\n");
}); });
+21 -17
View File
@@ -24,11 +24,11 @@ import * as cache from "./cache";
import { import {
getCurrentSnapshot, getCurrentSnapshot,
getConfig, getConfig,
getConfigExpression getConfigExpression,
} from "./local-cache"; } from "./local-cache";
import { import {
httpConnectionRequest, httpConnectionRequest,
udpConnectionRequest udpConnectionRequest,
} from "./connection-request"; } from "./connection-request";
import { Expression, Task } from "./types"; import { Expression, Task } from "./types";
import { flattenDevice } from "./mongodb-functions"; import { flattenDevice } from "./mongodb-functions";
@@ -36,7 +36,7 @@ import { evaluate } from "./common/expression";
export async function connectionRequest( export async function connectionRequest(
deviceId: string, deviceId: string,
device?: {} device?: Record<string, { value?: [boolean | number | string, string] }>
): Promise<void> { ): Promise<void> {
if (!device) { if (!device) {
const res = await db.devicesCollection.findOne({ _id: deviceId }); const res = await db.devicesCollection.findOne({ _id: deviceId });
@@ -137,7 +137,7 @@ export async function connectionRequest(
"FUNC", "FUNC",
"AUTH", "AUTH",
["PARAM", "username"], ["PARAM", "username"],
["PARAM", "password"] ["PARAM", "password"],
] as Expression; ] as Expression;
} }
@@ -171,8 +171,12 @@ export async function connectionRequest(
} }
} }
export async function watchTask(deviceId, taskId, timeout): Promise<string> { export async function watchTask(
await new Promise(resolve => setTimeout(resolve, 500)); deviceId: string,
taskId: string,
timeout: number
): Promise<string> {
await new Promise((resolve) => setTimeout(resolve, 500));
const task = await db.tasksCollection.findOne( const task = await db.tasksCollection.findOne(
{ _id: taskId }, { _id: taskId },
@@ -182,7 +186,7 @@ export async function watchTask(deviceId, taskId, timeout): Promise<string> {
const q = { _id: `${deviceId}:task_${taskId}` }; const q = { _id: `${deviceId}:task_${taskId}` };
const fault = await db.faultsCollection.findOne(q, { const fault = await db.faultsCollection.findOne(q, {
projection: { _id: 1 } projection: { _id: 1 },
}); });
if (fault) return "fault"; if (fault) return "fault";
@@ -194,7 +198,7 @@ export async function watchTask(deviceId, taskId, timeout): Promise<string> {
function sanitizeTask(task): void { function sanitizeTask(task): void {
task.timestamp = new Date(task.timestamp || Date.now()); task.timestamp = new Date(task.timestamp || Date.now());
if (task.expiry) { if (task.expiry) {
if (common.typeOf(task.expiry) === common.DATE_TYPE || isNaN(task.expiry)) if (task.expiry instanceof Date || isNaN(task.expiry))
task.expiry = new Date(task.expiry); task.expiry = new Date(task.expiry);
else task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000); else task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000);
} }
@@ -269,8 +273,8 @@ function sanitizeTask(task): void {
return task; return task;
} }
export async function insertTasks(tasks): Promise<Task[]> { export async function insertTasks(tasks: any[]): Promise<Task[]> {
if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) tasks = [tasks]; if (tasks && !Array.isArray(tasks)) tasks = [tasks];
else if (!tasks || tasks.length === 0) return tasks || []; else if (!tasks || tasks.length === 0) return tasks || [];
for (const task of tasks) { for (const task of tasks) {
@@ -278,7 +282,7 @@ export async function insertTasks(tasks): Promise<Task[]> {
if (task.uniqueKey) { if (task.uniqueKey) {
await db.tasksCollection.deleteOne({ await db.tasksCollection.deleteOne({
device: task.device, device: task.device,
uniqueKey: task.uniqueKey uniqueKey: task.uniqueKey,
}); });
} }
} }
@@ -286,20 +290,20 @@ export async function insertTasks(tasks): Promise<Task[]> {
return tasks; return tasks;
} }
export async function deleteDevice(deviceId): Promise<void> { export async function deleteDevice(deviceId: string): Promise<void> {
await Promise.all([ await Promise.all([
db.tasksCollection.deleteMany({ device: deviceId }), db.tasksCollection.deleteMany({ device: deviceId }),
db.devicesCollection.deleteOne({ _id: deviceId }), db.devicesCollection.deleteOne({ _id: deviceId }),
db.faultsCollection.deleteMany({ db.faultsCollection.deleteMany({
_id: { _id: {
$regex: `^${common.escapeRegExp(deviceId)}\\:` $regex: `^${common.escapeRegExp(deviceId)}\\:`,
} },
}), }),
db.operationsCollection.deleteMany({ db.operationsCollection.deleteMany({
_id: { _id: {
$regex: `^${common.escapeRegExp(deviceId)}\\:` $regex: `^${common.escapeRegExp(deviceId)}\\:`,
} },
}), }),
cache.del(`${deviceId}_tasks_faults_operations`) cache.del(`${deviceId}_tasks_faults_operations`),
]); ]);
} }
+15 -22
View File
@@ -19,11 +19,11 @@
import { createHash, randomBytes, pbkdf2 } from "crypto"; import { createHash, randomBytes, pbkdf2 } from "crypto";
function parseHeaderFeilds(str: string): {} { function parseHeaderFeilds(str: string): Record<string, string> {
const res = {}; const res = {};
const parts = str.split(","); const parts = str.split(",");
let part; let part: string;
while ((part = parts.shift()) != null) { while ((part = parts.shift()) != null) {
const name = part.split("=", 1)[0]; const name = part.split("=", 1)[0];
if (name.length === part.length) { if (name.length === part.length) {
@@ -52,7 +52,9 @@ function parseHeaderFeilds(str: string): {} {
return res; return res;
} }
export function parseAuthorizationHeader(authHeader): { method: string } { export function parseAuthorizationHeader(
authHeader: string
): { method: string } {
authHeader = authHeader.trim(); authHeader = authHeader.trim();
const method = authHeader.split(" ", 1)[0]; const method = authHeader.split(" ", 1)[0];
const res = { method: method }; const res = { method: method };
@@ -74,7 +76,9 @@ export function parseAuthorizationHeader(authHeader): { method: string } {
return res; return res;
} }
export function parseWwwAuthenticateHeader(authHeader): {} { export function parseWwwAuthenticateHeader(
authHeader: string
): Record<string, string> {
authHeader = authHeader.trim(); authHeader = authHeader.trim();
const method = authHeader.split(" ", 1)[0]; const method = authHeader.split(" ", 1)[0];
const res = { method: method }; const res = { method: method };
@@ -82,7 +86,7 @@ export function parseWwwAuthenticateHeader(authHeader): {} {
return res; return res;
} }
export function basic(username, password): string { export function basic(username: string, password: string): string {
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64"); return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
} }
@@ -99,20 +103,12 @@ export function digest(
nc?: string | Buffer nc?: string | Buffer
): string { ): string {
const ha1 = createHash("md5"); const ha1 = createHash("md5");
ha1 ha1.update(username).update(":").update(realm).update(":").update(password);
.update(username)
.update(":")
.update(realm)
.update(":")
.update(password);
// TODO support "MD5-sess" algorithm directive // TODO support "MD5-sess" algorithm directive
const ha1d = ha1.digest("hex"); const ha1d = ha1.digest("hex");
const ha2 = createHash("md5"); const ha2 = createHash("md5");
ha2 ha2.update(httpMethod).update(":").update(uri);
.update(httpMethod)
.update(":")
.update(uri);
if (qop === "auth-int") { if (qop === "auth-int") {
const bodyHash = createHash("md5") const bodyHash = createHash("md5")
@@ -124,10 +120,7 @@ export function digest(
const ha2d = ha2.digest("hex"); const ha2d = ha2.digest("hex");
const hash = createHash("md5"); const hash = createHash("md5");
hash hash.update(ha1d).update(":").update(nonce);
.update(ha1d)
.update(":")
.update(nonce);
if (qop) { if (qop) {
hash hash
.update(":") .update(":")
@@ -148,7 +141,7 @@ export function solveDigest(
uri: string | Buffer, uri: string | Buffer,
httpMethod: string | Buffer, httpMethod: string | Buffer,
body: string | Buffer, body: string | Buffer,
authHeader authHeader: Record<string, string>
): string { ): string {
const cnonce = randomBytes(8).toString("hex"); const cnonce = randomBytes(8).toString("hex");
const nc = "00000001"; const nc = "00000001";
@@ -185,7 +178,7 @@ export function solveDigest(
return authString; return authString;
} }
export function generateSalt(length): Promise<string> { export function generateSalt(length: number): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
randomBytes(length, (err, rand) => { randomBytes(length, (err, rand) => {
if (err) return void reject(err); if (err) return void reject(err);
@@ -194,7 +187,7 @@ export function generateSalt(length): Promise<string> {
}); });
} }
export function hashPassword(pass, salt): Promise<string> { export function hashPassword(pass: string, salt: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
pbkdf2(pass, salt, 10000, 128, "sha512", (err, hash) => { pbkdf2(pass, salt, 10000, 128, "sha512", (err, hash) => {
if (err) return void reject(err); if (err) return void reject(err);
+13 -12
View File
@@ -29,7 +29,7 @@ let mongoTimeOffset = 0;
export async function connect(): Promise<void> { export async function connect(): Promise<void> {
const MONGODB_CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL"); const MONGODB_CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL");
clientPromise = MongoClient.connect(MONGODB_CONNECTION_URL, { clientPromise = MongoClient.connect(MONGODB_CONNECTION_URL, {
useNewUrlParser: true useNewUrlParser: true,
}); });
const db = (await clientPromise).db(); const db = (await clientPromise).db();
mongoCollection = db.collection("cache"); mongoCollection = db.collection("cache");
@@ -43,7 +43,7 @@ export async function disconnect(): Promise<void> {
if (clientPromise) await (await clientPromise).close(); if (clientPromise) await (await clientPromise).close();
} }
export async function get(key): Promise<any> { export async function get(key: string | string[]): Promise<any> {
const expire = new Date(Date.now() - mongoTimeOffset); const expire = new Date(Date.now() - mongoTimeOffset);
if (Array.isArray(key)) { if (Array.isArray(key)) {
const res = await mongoCollection.find({ _id: { $in: key } }).toArray(); const res = await mongoCollection.find({ _id: { $in: key } }).toArray();
@@ -54,7 +54,7 @@ export async function get(key): Promise<any> {
}); });
const values = []; const values = [];
res.forEach(r => { res.forEach((r) => {
if (r["expire"] > expire) values[indices[r["_id"]]] = r["value"]; if (r["expire"] > expire) values[indices[r["_id"]]] = r["value"];
}); });
@@ -66,7 +66,7 @@ export async function get(key): Promise<any> {
} }
} }
export async function del(key): Promise<void> { export async function del(key: string | string[]): Promise<void> {
if (Array.isArray(key)) if (Array.isArray(key))
await mongoCollection.deleteMany({ _id: { $in: key } }); await mongoCollection.deleteMany({ _id: { $in: key } });
else await mongoCollection.deleteOne({ _id: key }); else await mongoCollection.deleteOne({ _id: key });
@@ -85,7 +85,7 @@ export async function set(
); );
} }
export async function pop(key): Promise<any> { export async function pop(key: string): Promise<any> {
const res = await mongoCollection.findOneAndDelete({ _id: key }); const res = await mongoCollection.findOneAndDelete({ _id: key });
if ( if (
@@ -98,16 +98,17 @@ export async function pop(key): Promise<any> {
return null; return null;
} }
export async function lock(lockName, ttl): Promise<Function> { export async function lock(
const token = Math.random() lockName: string,
.toString(36) ttl: number
.slice(2); ): Promise<(extendTtl?: number) => Promise<void>> {
const token = Math.random().toString(36).slice(2);
async function unlockOrExtend(extendTtl): Promise<void> { async function unlockOrExtend(extendTtl?: number): Promise<void> {
if (!extendTtl) { if (!extendTtl) {
const res = await mongoCollection.deleteOne({ const res = await mongoCollection.deleteOne({
_id: lockName, _id: lockName,
value: token value: token,
}); });
if (res["result"]["n"] !== 1) throw new Error("Lock expired"); if (res["result"]["n"] !== 1) throw new Error("Lock expired");
} else { } else {
@@ -131,7 +132,7 @@ export async function lock(lockName, ttl): Promise<Function> {
); );
} catch (err) { } catch (err) {
if (err && err.code === 11000) { if (err && err.code === 11000) {
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise((resolve) => setTimeout(resolve, 200));
return lock(lockName, ttl); return lock(lockName, ttl);
} }
} }
+9 -5
View File
@@ -41,7 +41,7 @@ function restartWorker(worker, code, signal): void {
message: "Worker died", message: "Worker died",
pid: worker.process.pid, pid: worker.process.pid,
exitCode: null, exitCode: null,
signal: null signal: null,
}; };
if (code != null) msg.exitCode = code; if (code != null) msg.exitCode = code;
@@ -57,7 +57,7 @@ function restartWorker(worker, code, signal): void {
min2 = 0, min2 = 0,
min3 = 0; min3 = 0;
crashes = crashes.filter(n => { crashes = crashes.filter((n) => {
if (n > now - 60000) ++min1; if (n > now - 60000) ++min1;
else if (n > now - 120000) ++min2; else if (n > now - 120000) ++min2;
else if (n > now - 180000) ++min3; else if (n > now - 180000) ++min3;
@@ -72,7 +72,7 @@ function restartWorker(worker, code, signal): void {
logger.error({ logger.error({
message: "Too many crashes, exiting", message: "Too many crashes, exiting",
pid: process.pid pid: process.pid,
}); });
return; return;
} }
@@ -89,7 +89,11 @@ function restartWorker(worker, code, signal): void {
}, respawnTimestamp - now); }, respawnTimestamp - now);
} }
export function start(workerCount, servicePort, serviceAddress): void { export function start(
workerCount: number,
servicePort: number,
serviceAddress: string
): void {
cluster.on("listening", (worker, address) => { cluster.on("listening", (worker, address) => {
if ( if (
(address.addressType === 4 || address.addressType === 6) && (address.addressType === 4 || address.addressType === 6) &&
@@ -100,7 +104,7 @@ export function start(workerCount, servicePort, serviceAddress): void {
message: "Worker listening", message: "Worker listening",
pid: worker.process.pid, pid: worker.process.pid,
address: address.address, address: address.address,
port: address.port port: address.port,
}); });
} }
}); });
+5 -15
View File
@@ -17,22 +17,12 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
export const UNDEFINED_TYPE = "[object Undefined]"; export function generateDeviceId(
export const NULL_TYPE = "[object Null]"; deviceIdStruct: Record<string, string>
export const BOOLEAN_TYPE = "[object Boolean]"; ): string {
export const NUMBER_TYPE = "[object Number]";
export const STRING_TYPE = "[object String]";
export const ARRAY_TYPE = "[object Array]";
export const OBJECT_TYPE = "[object Object]";
export const REGEXP_TYPE = "[object RegExp]";
export const DATE_TYPE = "[object Date]";
export const typeOf = (obj): string => Object.prototype.toString.call(obj);
export function generateDeviceId(deviceIdStruct): string {
// Escapes everything except alphanumerics and underscore // Escapes everything except alphanumerics and underscore
function esc(str): string { function esc(str): string {
return str.replace(/[^A-Za-z0-9_]/g, chr => { return str.replace(/[^A-Za-z0-9_]/g, (chr) => {
const buf = Buffer.from(chr, "utf8"); const buf = Buffer.from(chr, "utf8");
let rep = ""; let rep = "";
for (const b of buf) rep += "%" + b.toString(16).toUpperCase(); for (const b of buf) rep += "%" + b.toString(16).toUpperCase();
@@ -54,6 +44,6 @@ export function generateDeviceId(deviceIdStruct): string {
} }
// Source: http://stackoverflow.com/a/6969486 // Source: http://stackoverflow.com/a/6969486
export function escapeRegExp(str): string { export function escapeRegExp(str: string): string {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&"); return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
} }
+5 -5
View File
@@ -23,13 +23,13 @@ import { evaluate, or } from "./expression";
export default class Authorizer { export default class Authorizer {
private permissionSets: PermissionSet[]; private permissionSets: PermissionSet[];
private validatorCache: WeakMap< private validatorCache: WeakMap<
object, any,
(mutationType, mutation, any) => boolean (mutationType, mutation, any) => boolean
>; >;
private hasAccessCache: Map<string, boolean>; private hasAccessCache: Map<string, boolean>;
private getFilterCache: Map<string, Expression>; private getFilterCache: Map<string, Expression>;
public constructor(permissionSets) { public constructor(permissionSets: PermissionSet[]) {
this.permissionSets = permissionSets; this.permissionSets = permissionSets;
this.validatorCache = new WeakMap(); this.validatorCache = new WeakMap();
this.hasAccessCache = new Map(); this.hasAccessCache = new Map();
@@ -77,8 +77,8 @@ export default class Authorizer {
} }
public getValidator( public getValidator(
resourceType, resourceType: string,
resource resource: unknown
): (mutationType: string, mutation?: any, args?: any) => boolean { ): (mutationType: string, mutation?: any, args?: any) => boolean {
if (this.validatorCache.has(resource)) if (this.validatorCache.has(resource))
return this.validatorCache.get(resource); return this.validatorCache.get(resource);
@@ -108,7 +108,7 @@ export default class Authorizer {
mutation, mutation,
resourceType, resourceType,
object: resource, object: resource,
options: any options: any,
}; };
const valueFunction = (paramName): any => { const valueFunction = (paramName): any => {
+12 -12
View File
@@ -65,7 +65,7 @@ function likePatternIncludes(pat1, pat2, idx1 = 0, idx2 = 0): boolean {
export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } { export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
const ranges = new Map(); const ranges = new Map();
const likePatterns = new Map(); const likePatterns = new Map();
map(exp, e => { map(exp, (e) => {
if (!Array.isArray(e)) return e; if (!Array.isArray(e)) return e;
if (e[0] === "LIKE" || e[0] === "NOT LIKE") { if (e[0] === "LIKE" || e[0] === "NOT LIKE") {
if ( if (
@@ -151,7 +151,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
if (to == null) vars.push(`${lhs}:${range[range.length - 1]}--`); if (to == null) vars.push(`${lhs}:${range[range.length - 1]}--`);
return vars.map(key => { return vars.map((key) => {
let f = expressions.get(key); let f = expressions.get(key);
if (!f) { if (!f) {
expressions.set(key, (f = expressions.size + 1)); expressions.set(key, (f = expressions.size + 1));
@@ -172,7 +172,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
vars.push(`${lhs}:like:${JSON.stringify(p)}`); vars.push(`${lhs}:like:${JSON.stringify(p)}`);
} }
return vars.map(key => { return vars.map((key) => {
let f = expressions.get(key); let f = expressions.get(key);
if (!f) expressions.set(key, (f = expressions.size + 1)); if (!f) expressions.set(key, (f = expressions.size + 1));
return f; return f;
@@ -184,11 +184,11 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
} }
const comparisonOperators: ComparisonOperators = { const comparisonOperators: ComparisonOperators = {
"=": v => [v, true, v, true], "=": (v) => [v, true, v, true],
">": v => [v, false, null, null], ">": (v) => [v, false, null, null],
">=": v => [v, true, null, null], ">=": (v) => [v, true, null, null],
"<": v => [null, null, v, false], "<": (v) => [null, null, v, false],
"<=": v => [null, null, v, true] "<=": (v) => [null, null, v, true],
}; };
function recursive(clause, negate): CNF { function recursive(clause, negate): CNF {
@@ -211,7 +211,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
JSON.stringify(clause[1]), JSON.stringify(clause[1]),
...comparisonOperators["="](clause[2]) ...comparisonOperators["="](clause[2])
); );
if (!negate) return vars.map(x => [0 - x]); if (!negate) return vars.map((x) => [0 - x]);
else return [vars]; else return [vars];
} else if (op === "IS NULL") { } else if (op === "IS NULL") {
const v = getVariable(JSON.stringify(clause[1]), "null"); const v = getVariable(JSON.stringify(clause[1]), "null");
@@ -227,7 +227,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
JSON.stringify(clause[1]), JSON.stringify(clause[1]),
...comparisonOperators[op](clause[2]) ...comparisonOperators[op](clause[2])
); );
if (negate) return vars.map(x => [0 - x]); if (negate) return vars.map((x) => [0 - x]);
else return [vars]; else return [vars];
} else if (!Array.isArray(clause[1])) { } else if (!Array.isArray(clause[1])) {
op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op; op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op;
@@ -235,7 +235,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
JSON.stringify(clause[2]), JSON.stringify(clause[2]),
...comparisonOperators[op](clause[1]) ...comparisonOperators[op](clause[1])
); );
if (negate) return vars.map(x => [0 - x]); if (negate) return vars.map((x) => [0 - x]);
else return [vars]; else return [vars];
} else { } else {
let lhs = JSON.stringify(clause[1]); let lhs = JSON.stringify(clause[1]);
@@ -264,7 +264,7 @@ export function booleanCnf(exp: Expression): { vars: number; clauses: CNF } {
clause[2], clause[2],
clause[3] clause[3]
); );
if (negate) return vars.map(x => [0 - x]); if (negate) return vars.map((x) => [0 - x]);
else return [vars]; else return [vars];
} else { } else {
const v = getVariable(JSON.stringify(clause), ""); const v = getVariable(JSON.stringify(clause), "");
+40 -43
View File
@@ -27,7 +27,7 @@ function interpretEscapes(str): string {
f: "\f", f: "\f",
n: "\n", n: "\n",
r: "\r", r: "\r",
t: "\t" t: "\t",
}; };
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => { return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
const type = escape.charAt(0); const type = escape.charAt(0);
@@ -40,7 +40,10 @@ function interpretEscapes(str): string {
}); });
} }
export function map(exp, callback): Expression { export function map(
exp: Expression,
callback: (exp: Expression) => Expression
): Expression {
if (!Array.isArray(exp)) return callback(exp); if (!Array.isArray(exp)) return callback(exp);
let clone; let clone;
@@ -55,7 +58,10 @@ export function map(exp, callback): Expression {
return callback(clone || exp); return callback(clone || exp);
} }
export async function mapAsync(exp, callback): Promise<Expression> { export async function mapAsync(
exp: Expression,
callback: (exp: Expression) => Promise<Expression>
): Promise<Expression> {
if (!Array.isArray(exp)) return callback(exp); if (!Array.isArray(exp)) return callback(exp);
let clone; let clone;
@@ -70,7 +76,7 @@ export async function mapAsync(exp, callback): Promise<Expression> {
return callback(clone || exp); return callback(clone || exp);
} }
function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<{}> { function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<unknown> {
return parsimmon.seqMap( return parsimmon.seqMap(
nextParser, nextParser,
parsimmon.seq(operatorsParser, nextParser).many(), parsimmon.seq(operatorsParser, nextParser).many(),
@@ -86,7 +92,7 @@ function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<{}> {
} }
const lang = parsimmon.createLanguage({ const lang = parsimmon.createLanguage({
ComparisonOperator: function() { ComparisonOperator: function () {
return parsimmon return parsimmon
.alt( .alt(
parsimmon.string(">="), parsimmon.string(">="),
@@ -98,13 +104,10 @@ const lang = parsimmon.createLanguage({
) )
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
}, },
LikeOperator: function() { LikeOperator: function () {
return parsimmon return parsimmon
.alt( .alt(
parsimmon parsimmon.regexp(/like/i).result("LIKE").desc("LIKE"),
.regexp(/like/i)
.result("LIKE")
.desc("LIKE"),
parsimmon parsimmon
.regexp(/not\s+like/i) .regexp(/not\s+like/i)
.result("NOT LIKE") .result("NOT LIKE")
@@ -113,7 +116,7 @@ const lang = parsimmon.createLanguage({
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
}, },
IsNullOperator: function() { IsNullOperator: function () {
return parsimmon return parsimmon
.alt( .alt(
parsimmon parsimmon
@@ -128,7 +131,7 @@ const lang = parsimmon.createLanguage({
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
}, },
NotOperator: function() { NotOperator: function () {
return parsimmon return parsimmon
.regexp(/not/i) .regexp(/not/i)
.result("NOT") .result("NOT")
@@ -136,7 +139,7 @@ const lang = parsimmon.createLanguage({
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.desc("NOT"); .desc("NOT");
}, },
AndOperator: function() { AndOperator: function () {
return parsimmon return parsimmon
.regexp(/and/i) .regexp(/and/i)
.result("AND") .result("AND")
@@ -144,7 +147,7 @@ const lang = parsimmon.createLanguage({
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.desc("AND"); .desc("AND");
}, },
OrOperator: function() { OrOperator: function () {
return parsimmon return parsimmon
.regexp(/or/i) .regexp(/or/i)
.result("OR") .result("OR")
@@ -152,7 +155,7 @@ const lang = parsimmon.createLanguage({
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.desc("OR"); .desc("OR");
}, },
Parameter: function(r) { Parameter: function (r) {
return parsimmon return parsimmon
.alt( .alt(
parsimmon.regexp(/[a-zA-Z0-9_.*-]+/), parsimmon.regexp(/[a-zA-Z0-9_.*-]+/),
@@ -162,48 +165,42 @@ const lang = parsimmon.createLanguage({
) )
) )
.atLeast(1) .atLeast(1)
.map(x => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]]) .map((x) => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]])
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.desc("parameter"); .desc("parameter");
}, },
StringValueSql: function() { StringValueSql: function () {
return parsimmon return parsimmon
.regexp(/'([^']*)'/, 1) .regexp(/'([^']*)'/, 1)
.atLeast(1) .atLeast(1)
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.map(s => s.join("'")) .map((s) => s.join("'"))
.desc("string"); .desc("string");
}, },
StringValueJs: function() { StringValueJs: function () {
return parsimmon return parsimmon
.regexp(/"((?:\\.|.)*?)"/, 1) .regexp(/"((?:\\.|.)*?)"/, 1)
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.map(interpretEscapes) .map(interpretEscapes)
.desc("string"); .desc("string");
}, },
NumberValue: function() { NumberValue: function () {
return parsimmon return parsimmon
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/) .regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
.skip(parsimmon.optWhitespace) .skip(parsimmon.optWhitespace)
.map(Number) .map(Number)
.desc("number"); .desc("number");
}, },
BooleanValue: function() { BooleanValue: function () {
return parsimmon return parsimmon
.alt( .alt(
parsimmon parsimmon.regexp(/true/i).result(true).desc("TRUE"),
.regexp(/true/i) parsimmon.regexp(/false/i).result(false).desc("FALSE")
.result(true)
.desc("TRUE"),
parsimmon
.regexp(/false/i)
.result(false)
.desc("FALSE")
) )
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
}, },
NullValue: function() { NullValue: function () {
return parsimmon return parsimmon
.regexp(/null/i) .regexp(/null/i)
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
@@ -211,7 +208,7 @@ const lang = parsimmon.createLanguage({
.result(null) .result(null)
.desc("NULL"); .desc("NULL");
}, },
FuncValue: function(r) { FuncValue: function (r) {
return parsimmon.seqMap( return parsimmon.seqMap(
parsimmon parsimmon
.regexp(/([a-zA-Z0-9_]+)/, 1) .regexp(/([a-zA-Z0-9_]+)/, 1)
@@ -226,7 +223,7 @@ const lang = parsimmon.createLanguage({
(f, args) => ["FUNC", f.toUpperCase()].concat(args) (f, args) => ["FUNC", f.toUpperCase()].concat(args)
); );
}, },
Value: function(r) { Value: function (r) {
return parsimmon.alt( return parsimmon.alt(
r.NullValue, r.NullValue,
r.BooleanValue, r.BooleanValue,
@@ -236,7 +233,7 @@ const lang = parsimmon.createLanguage({
r.FuncValue r.FuncValue
); );
}, },
ValueExpression: function(r) { ValueExpression: function (r) {
return binaryLeft( return binaryLeft(
parsimmon.string("||").skip(parsimmon.optWhitespace), parsimmon.string("||").skip(parsimmon.optWhitespace),
binaryLeft( binaryLeft(
@@ -259,7 +256,7 @@ const lang = parsimmon.createLanguage({
) )
); );
}, },
Comparison: function(r) { Comparison: function (r) {
return parsimmon.alt( return parsimmon.alt(
parsimmon.seqMap(r.ValueExpression, r.IsNullOperator, (p, o) => [o, p]), parsimmon.seqMap(r.ValueExpression, r.IsNullOperator, (p, o) => [o, p]),
parsimmon.seqMap( parsimmon.seqMap(
@@ -289,8 +286,8 @@ const lang = parsimmon.createLanguage({
) )
); );
}, },
Expression: function(r) { Expression: function (r) {
function unary(operatorsParser, nextParser): parsimmon.Parser<{}> { function unary(operatorsParser, nextParser): parsimmon.Parser<unknown> {
return parsimmon.seq(operatorsParser, nextParser).or(nextParser); return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
} }
@@ -301,7 +298,7 @@ const lang = parsimmon.createLanguage({
unary(r.NotOperator, r.Comparison.or(r.ValueExpression)) unary(r.NotOperator, r.Comparison.or(r.ValueExpression))
) )
).trim(parsimmon.optWhitespace); ).trim(parsimmon.optWhitespace);
} },
}); });
export function parse(str: string): Expression { export function parse(str: string): Expression {
@@ -309,7 +306,7 @@ export function parse(str: string): Expression {
return lang.Expression.tryParse(str); return lang.Expression.tryParse(str);
} }
export function stringify(exp, level = 0): string { export function stringify(exp: Expression, level = 0): string {
if (!Array.isArray(exp)) return JSON.stringify(exp); if (!Array.isArray(exp)) return JSON.stringify(exp);
const opLevels = { const opLevels = {
@@ -330,7 +327,7 @@ export function stringify(exp, level = 0): string {
"+": 31, "+": 31,
"-": 31, "-": 31,
"*": 32, "*": 32,
"/": 32 "/": 32,
}; };
const op = exp[0].toUpperCase(); const op = exp[0].toUpperCase();
@@ -344,7 +341,7 @@ export function stringify(exp, level = 0): string {
return wrap( return wrap(
`${exp[1]}(${exp `${exp[1]}(${exp
.slice(2) .slice(2)
.map(e => stringify(e)) .map((e) => stringify(e))
.join(", ")})` .join(", ")})`
); );
} else if (op === "PARAM") { } else if (op === "PARAM") {
@@ -354,7 +351,7 @@ export function stringify(exp, level = 0): string {
return wrap( return wrap(
exp[1] exp[1]
.slice(1) .slice(1)
.map(p => { .map((p) => {
if (typeof p === "string") return p; if (typeof p === "string") return p;
else return `{${stringify(p)}}`; else return `{${stringify(p)}}`;
}) })
@@ -393,7 +390,7 @@ export function stringify(exp, level = 0): string {
} }
} }
export function parseLikePattern(pat, esc): string[] { export function parseLikePattern(pat: string, esc: string): string[] {
const chars = pat.split(""); const chars = pat.split("");
for (let i = 0; i < chars.length; ++i) { for (let i = 0; i < chars.length; ++i) {
@@ -408,5 +405,5 @@ export function parseLikePattern(pat, esc): string[] {
while (chars[i + 1] === "%") chars[++i] = ""; while (chars[i + 1] === "%") chars[++i] = "";
} }
} }
return chars.filter(c => c); return chars.filter((c) => c);
} }
+36 -26
View File
@@ -24,7 +24,7 @@ import { Expression } from "../types";
const isArray = Array.isArray; const isArray = Array.isArray;
const regExpCache = new WeakMap<object, RegExp>(); const regExpCache = new WeakMap<any, RegExp>();
const REDUCE_SKIP = {}; const REDUCE_SKIP = {};
function reduce(exp, callback): Expression { function reduce(exp, callback): Expression {
@@ -44,7 +44,7 @@ function reduce(exp, callback): Expression {
return exp; return exp;
} }
export function likePatternToRegExp(pat, esc = "", flags = ""): RegExp { export function likePatternToRegExp(pat: string, esc = "", flags = ""): RegExp {
const convChars = { const convChars = {
"-": "\\-", "-": "\\-",
"/": "\\/", "/": "\\/",
@@ -63,11 +63,11 @@ export function likePatternToRegExp(pat, esc = "", flags = ""): RegExp {
"{": "\\{", "{": "\\{",
"}": "\\}", "}": "\\}",
"\\%": ".*", "\\%": ".*",
"\\_": "." "\\_": ".",
}; };
let chars = parseLikePattern(pat, esc); let chars = parseLikePattern(pat, esc);
if (!chars.length) return new RegExp("^$", flags); if (!chars.length) return new RegExp("^$", flags);
chars = chars.map(c => convChars[c] || c); chars = chars.map((c) => convChars[c] || c);
chars[0] = chars[0] === ".*" ? "" : "^" + chars[0]; chars[0] = chars[0] === ".*" ? "" : "^" + chars[0];
const l = chars.length - 1; const l = chars.length - 1;
chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$"; chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$";
@@ -195,14 +195,24 @@ function evalExp(e: Expression): Expression {
} }
export function evaluate( export function evaluate(
exp, exp: Expression,
obj, obj: Record<string, unknown> | ((exp: string) => Expression),
now: number, now: number,
cb?: Function cb?: (exp: Expression) => Expression
): string | number | boolean | null; ): string | number | boolean | null;
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression; export function evaluate(
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression { exp: Expression,
return map(exp, e => { obj?: Record<string, unknown> | ((exp: string) => Expression),
now?: number,
cb?: (exp: Expression) => Expression
): Expression;
export function evaluate(
exp: Expression,
obj?: Record<string, unknown> | ((exp: string) => Expression),
now?: number,
cb?: (exp: Expression) => Expression
): Expression {
return map(exp, (e) => {
if (cb) e = cb(e); if (cb) e = cb(e);
if (!isArray(e)) return e; if (!isArray(e)) return e;
@@ -233,24 +243,24 @@ export function evaluate(exp, obj?, now?: number, cb?: Function): Expression {
} }
export async function evaluateAsync( export async function evaluateAsync(
exp, exp: Expression,
obj, obj: Record<string, unknown>,
now: number, now: number,
cb?: Function cb?: (exp: Expression) => Promise<Expression>
): Promise<string | number | boolean | null>; ): Promise<string | number | boolean | null>;
export async function evaluateAsync( export async function evaluateAsync(
exp, exp: Expression,
obj?, obj?: Record<string, unknown>,
now?: number, now?: number,
cb?: Function cb?: (exp: Expression) => Promise<Expression>
): Promise<Expression>; ): Promise<Expression>;
export async function evaluateAsync( export async function evaluateAsync(
exp, exp: Expression,
obj?, obj?: Record<string, unknown>,
now?: number, now?: number,
cb?: Function cb?: (exp: Expression) => Promise<Expression>
): Promise<Expression> { ): Promise<Expression> {
return mapAsync(exp, async e => { return mapAsync(exp, async (e) => {
if (cb) e = await cb(e); if (cb) e = await cb(e);
if (!isArray(e)) return e; if (!isArray(e)) return e;
@@ -269,8 +279,8 @@ export async function evaluateAsync(
if (obj && !isArray(e[1])) { if (obj && !isArray(e[1])) {
let v = obj[e[1]]; let v = obj[e[1]];
if (v == null) return null; if (v == null) return null;
if (typeof v === "object") v = v.value ? v.value[0] : null; if (typeof v === "object") v = v["value"] ? v["value"][0] : null;
return v; return v as Expression;
} }
} }
return evalExp(e); return evalExp(e);
@@ -307,21 +317,21 @@ export function or(exp1: Expression, exp2: Expression): Expression {
return res; return res;
} }
export function not(exp): Expression { export function not(exp: Expression): Expression {
if (isArray(exp) && exp[0] === "NOT") return exp[1]; if (isArray(exp) && exp[0] === "NOT") return exp[1];
return ["NOT", exp]; return ["NOT", exp];
} }
export function subset(exp1, exp2): boolean { export function subset(exp1: Expression, exp2: Expression): boolean {
const e = evaluate(["NOT", ["OR", ["NOT", exp1], exp2]]); const e = evaluate(["NOT", ["OR", ["NOT", exp1], exp2]]);
if (!isArray(e)) return !e; if (!isArray(e)) return !e;
const { vars, clauses } = booleanCnf(e); const { vars, clauses } = booleanCnf(e);
return !naiveDpll(clauses, vars); return !naiveDpll(clauses, vars);
} }
export function extractParams(exp): Expression[] { export function extractParams(exp: Expression): Expression[] {
const params = new Set<Expression>(); const params = new Set<Expression>();
map(exp, e => { map(exp, (e) => {
if (isArray(e) && e[0] === "PARAM") params.add(e[1]); if (isArray(e) && e[0] === "PARAM") params.add(e[1]);
return e; return e;
}); });
+1 -1
View File
@@ -39,7 +39,7 @@ function getKey(obj): string {
return k; return k;
} }
export default function memoize<T extends Function>(func: T): T { export default function memoize<T extends (...args: any[]) => any>(func: T): T {
const funcKey = getKey(func); const funcKey = getKey(func);
return ((...args) => { return ((...args) => {
const key = JSON.stringify(args.map(getKey)) + funcKey; const key = JSON.stringify(args.map(getKey)) + funcKey;
+7 -7
View File
@@ -73,8 +73,8 @@ export default class PathSet {
public find( public find(
path: Path, path: Path,
superset: boolean = false, superset = false,
subset: boolean = false, subset = false,
depth: number = path.length depth: number = path.length
): Path[] { ): Path[] {
if (path.alias) throw new Error("PathSet does not support aliased paths"); if (path.alias) throw new Error("PathSet does not support aliased paths");
@@ -106,24 +106,24 @@ export default class PathSet {
if (!fragmentIndexSet1) { if (!fragmentIndexSet1) {
if (!fragmentIndexSet2) return []; if (!fragmentIndexSet2) return [];
if (!res) res = [...fragmentIndexSet2]; if (!res) res = [...fragmentIndexSet2];
else res = res.filter(r => fragmentIndexSet2.has(r)); else res = res.filter((r) => fragmentIndexSet2.has(r));
} else if (!fragmentIndexSet2) { } else if (!fragmentIndexSet2) {
if (!res) res = [...fragmentIndexSet1]; if (!res) res = [...fragmentIndexSet1];
else res = res.filter(r => fragmentIndexSet1.has(r)); else res = res.filter((r) => fragmentIndexSet1.has(r));
} else { } else {
if (!res) { if (!res) {
res = [...fragmentIndexSet1, ...fragmentIndexSet2]; res = [...fragmentIndexSet1, ...fragmentIndexSet2];
} else { } else {
res = res.filter( res = res.filter(
r => fragmentIndexSet1.has(r) || fragmentIndexSet2.has(r) (r) => fragmentIndexSet1.has(r) || fragmentIndexSet2.has(r)
); );
} }
} }
if (!res.length) return res; if (!res.length) return res;
} }
if (!res) res = [].concat(...lengthIndex.map(a => [...a])); if (!res) res = [].concat(...lengthIndex.map((a) => [...a]));
else res = res.filter(r => lengthIndex.some(a => a.has(r))); else res = res.filter((r) => lengthIndex.some((a) => a.has(r)));
return res; return res;
} }
+3 -3
View File
@@ -119,7 +119,7 @@ export default class Path {
if (Array.isArray(s)) { if (Array.isArray(s)) {
alias |= 1 << i; alias |= 1 << i;
const parts = s.map( const parts = s.map(
al => `${al[0].toString()}:${JSON.stringify(al[1])}` (al) => `${al[0].toString()}:${JSON.stringify(al[1])}`
); );
return `[${parts.join(",")}]`; return `[${parts.join(",")}]`;
} else if (s === "*") { } else if (s === "*") {
@@ -160,7 +160,7 @@ export default class Path {
return this._string; return this._string;
} }
public slice(start: number = 0, end: number = this.segments.length): Path { public slice(start = 0, end: number = this.segments.length): Path {
if (start < 0) start = Math.max(0, this.segments.length + start); if (start < 0) start = Math.max(0, this.segments.length + start);
if (end < 0) end = Math.max(0, this.segments.length + end); if (end < 0) end = Math.max(0, this.segments.length + end);
@@ -212,7 +212,7 @@ export default class Path {
public stripAlias(): Path { public stripAlias(): Path {
if (!this.alias) return this; if (!this.alias) return this;
const segments = this.segments.map(s => (Array.isArray(s) ? "*" : s)); const segments = this.segments.map((s) => (Array.isArray(s) ? "*" : s));
const str = segments.join("."); const str = segments.join(".");
let path = cache1.get(str); let path = cache1.get(str);
+1 -1
View File
@@ -168,7 +168,7 @@ function unitPropagate(clauses, varVals): number[] | 1 | -1 {
return derivedVars; return derivedVars;
} }
export function naiveDpll(clauses, maxvarnr): boolean { export function naiveDpll(clauses: unknown[], maxvarnr: number): boolean {
// variable values are 0 if not set, 1 if positive, -1 if negative // variable values are 0 if not set, 1 if positive, -1 if negative
const varVals = new Int32Array(maxvarnr + 1); const varVals = new Int32Array(maxvarnr + 1);
return satisfiableAt(clauses, varVals, 0, 0); return satisfiableAt(clauses, varVals, 0, 0);
+12 -36
View File
@@ -38,7 +38,7 @@ const options = {
EXT_DIR: { type: "path", default: resolve(ROOT_DIR, "config/ext") }, EXT_DIR: { type: "path", default: resolve(ROOT_DIR, "config/ext") },
MONGODB_CONNECTION_URL: { MONGODB_CONNECTION_URL: {
type: "string", type: "string",
default: "mongodb://127.0.0.1/genieacs" default: "mongodb://127.0.0.1/genieacs",
}, },
CWMP_WORKER_PROCESSES: { type: "int", default: 0 }, CWMP_WORKER_PROCESSES: { type: "int", default: 0 },
@@ -100,7 +100,7 @@ const options = {
MAX_COMMIT_ITERATIONS: { type: "int", default: 32 }, MAX_COMMIT_ITERATIONS: { type: "int", default: 32 },
// Should probably never be changed // Should probably never be changed
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 } DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 },
}; };
const allConfig: { [name: string]: string | number } = {}; const allConfig: { [name: string]: string | number } = {};
@@ -112,37 +112,17 @@ function setConfig(name, value, commandLineArgument = false): boolean {
if (name === "CONFIG_DIR" || name === "config-dir") if (name === "CONFIG_DIR" || name === "config-dir")
configDir = configDir || resolve(ROOT_DIR, value); configDir = configDir || resolve(ROOT_DIR, value);
if (name === "CWMP_SSL" || name === "cwmp-ssl") { if (name === "CWMP_SSL" || name === "cwmp-ssl")
cwmpSsl = cwmpSsl = cwmpSsl || String(value).toLowerCase().trim();
cwmpSsl ||
String(value)
.toLowerCase()
.trim();
}
if (name === "NBI_SSL" || name === "nbi-ssl") { if (name === "NBI_SSL" || name === "nbi-ssl")
nbiSsl = nbiSsl = nbiSsl || String(value).toLowerCase().trim();
nbiSsl ||
String(value)
.toLowerCase()
.trim();
}
if (name === "FS_SSL" || name === "fs-ssl") { if (name === "FS_SSL" || name === "fs-ssl")
fsSsl = fsSsl = fsSsl || String(value).toLowerCase().trim();
fsSsl ||
String(value)
.toLowerCase()
.trim();
}
if (name === "UI_SSL" || name === "ui-ssl") { if (name === "UI_SSL" || name === "ui-ssl")
uiSsl = uiSsl = uiSsl || String(value).toLowerCase().trim();
uiSsl ||
String(value)
.toLowerCase()
.trim();
}
if (name === "FS_HOSTNAME" || name === "fs-hostname") if (name === "FS_HOSTNAME" || name === "fs-hostname")
fsHostname = fsHostname || String(value).trim(); fsHostname = fsHostname || String(value).trim();
@@ -170,11 +150,7 @@ function setConfig(name, value, commandLineArgument = false): boolean {
case "int": case "int":
return Number(val); return Number(val);
case "bool": case "bool":
return ["true", "1"].includes( return ["true", "1"].includes(String(val).trim().toLowerCase());
String(val)
.trim()
.toLowerCase()
);
case "string": case "string":
return String(val); return String(val);
case "path": case "path":
@@ -308,7 +284,7 @@ export function get(
return null; return null;
} }
export function getDefault(optionName): string | number | boolean { export function getDefault(optionName: string): string | number | boolean {
const option = options[optionName]; const option = options[optionName];
if (!option) return null; if (!option) return null;
+11 -11
View File
@@ -65,10 +65,10 @@ function httpGet(
timeout, timeout,
_debug: boolean, _debug: boolean,
deviceId: string deviceId: string
): Promise<{ statusCode: number; headers: {} }> { ): Promise<{ statusCode: number; headers: http.IncomingHttpHeaders }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = http const req = http
.get(options, res => { .get(options, (res) => {
res.resume(); res.resume();
resolve({ statusCode: res.statusCode, headers: res.headers }); resolve({ statusCode: res.statusCode, headers: res.headers });
if (_debug) { if (_debug) {
@@ -76,12 +76,12 @@ function httpGet(
debug.incomingHttpResponse(res, deviceId, null); debug.incomingHttpResponse(res, deviceId, null);
} }
}) })
.on("error", err => { .on("error", (err) => {
req.abort(); req.abort();
reject(new Error("Device is offline")); reject(new Error("Device is offline"));
if (_debug) debug.outgoingHttpRequestError(req, deviceId, options, err); if (_debug) debug.outgoingHttpRequestError(req, deviceId, options, err);
}) })
.on("socket", socket => { .on("socket", (socket) => {
socket.setTimeout(timeout); socket.setTimeout(timeout);
socket.on("timeout", () => { socket.on("timeout", () => {
req.abort(); req.abort();
@@ -104,10 +104,10 @@ export async function httpConnectionRequest(
options.agent = new http.Agent({ options.agent = new http.Agent({
maxSockets: 1, maxSockets: 1,
keepAlive: true keepAlive: true,
}); });
let authHeader: {}; let authHeader: Record<string, string>;
let username: string; let username: string;
let password: string; let password: string;
@@ -121,8 +121,8 @@ export async function httpConnectionRequest(
opts = Object.assign( opts = Object.assign(
{ {
headers: { headers: {
Authorization: auth.basic(username || "", password || "") Authorization: auth.basic(username || "", password || ""),
} },
}, },
options options
); );
@@ -137,8 +137,8 @@ export async function httpConnectionRequest(
"GET", "GET",
null, null,
authHeader authHeader
) ),
} },
}, },
options options
); );
@@ -172,7 +172,7 @@ export async function httpConnectionRequest(
export async function udpConnectionRequest( export async function udpConnectionRequest(
address: string, address: string,
authExp: Expression, authExp: Expression,
sourcePort: number = 0, sourcePort = 0,
_debug: boolean, _debug: boolean,
deviceId: string deviceId: string
): Promise<void> { ): Promise<void> {
+66 -65
View File
@@ -43,7 +43,8 @@ import {
Expression, Expression,
Task, Task,
SoapMessage, SoapMessage,
InformRequest InformRequest,
AcsResponse,
} from "./types"; } from "./types";
import { IncomingMessage, ServerResponse } from "http"; import { IncomingMessage, ServerResponse } from "http";
import { Readable } from "stream"; import { Readable } from "stream";
@@ -67,7 +68,7 @@ const stats = {
concurrentRequests: 0, concurrentRequests: 0,
totalRequests: 0, totalRequests: 0,
droppedRequests: 0, droppedRequests: 0,
initiatedSessions: 0 initiatedSessions: 0,
}; };
async function authenticate( async function authenticate(
@@ -195,14 +196,14 @@ async function writeResponse(
if (connection.destroyed) { if (connection.destroyed) {
logger.accessError({ logger.accessError({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "Connection dropped" message: "Connection dropped",
}); });
} else if (close) { } else if (close) {
const isNew = await endSession(sessionContext); const isNew = await endSession(sessionContext);
if (isNew) { if (isNew) {
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "New device registered" message: "New device registered",
}); });
} }
} else { } else {
@@ -265,7 +266,7 @@ function recordFault(
message: "Channel has faulted", message: "Channel has faulted",
fault: fault, fault: fault,
channel: channel, channel: channel,
retries: sessionContext.retries[channel] retries: sessionContext.retries[channel],
}); });
} }
@@ -288,7 +289,7 @@ function recordFault(
async function inform( async function inform(
sessionContext: SessionContext, sessionContext: SessionContext,
rpc: SoapMessage rpc: SoapMessage
): Promise<{ code: number; headers: {}; data: string }> { ): Promise<{ code: number; headers: Record<string, string>; data: string }> {
const acsResponse = await session.inform( const acsResponse = await session.inform(
sessionContext, sessionContext,
rpc.cpeRequest as InformRequest rpc.cpeRequest as InformRequest
@@ -297,7 +298,7 @@ async function inform(
const res = soap.response({ const res = soap.response({
id: rpc.id, id: rpc.id,
acsResponse: acsResponse, acsResponse: acsResponse,
cwmpVersion: sessionContext.cwmpVersion cwmpVersion: sessionContext.cwmpVersion,
}); });
const cookiesPath = localCache.getConfig( const cookiesPath = localCache.getConfig(
@@ -305,7 +306,7 @@ async function inform(
"cwmp.cookiesPath", "cwmp.cookiesPath",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
e => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e)
); );
if (cookiesPath) { if (cookiesPath) {
@@ -329,7 +330,7 @@ async function transferComplete(sessionContext, rpc): Promise<void> {
logger.accessWarn({ logger.accessWarn({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "Unrecognized command key", message: "Unrecognized command key",
rpc: rpc rpc: rpc,
}); });
} }
@@ -346,7 +347,7 @@ async function transferComplete(sessionContext, rpc): Promise<void> {
const res = soap.response({ const res = soap.response({
id: rpc.id, id: rpc.id,
acsResponse: acsResponse, acsResponse: acsResponse,
cwmpVersion: sessionContext.cwmpVersion cwmpVersion: sessionContext.cwmpVersion,
}); });
return writeResponse(sessionContext, res); return writeResponse(sessionContext, res);
@@ -397,7 +398,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
"cwmp.retryDelay", "cwmp.retryDelay",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
e => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e)
); );
if (sessionContext.faults) { if (sessionContext.faults) {
@@ -466,13 +467,13 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
} }
} }
const declarations = Object.values(parameters).map(v => ({ const declarations = Object.values(parameters).map((v) => ({
path: v, path: v,
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: null, attrSet: null,
defer: true defer: true,
})); }));
const { fault: flt, rpcId: reqId, rpc: acsReq } = await session.rpcRequest( const { fault: flt, rpcId: reqId, rpc: acsReq } = await session.rpcRequest(
@@ -496,7 +497,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
const appendProvisionsToFaults = {}; const appendProvisionsToFaults = {};
for (const p of filteredPresets) { for (const p of filteredPresets) {
if ( if (
evaluate(p.precondition, {}, sessionContext.timestamp, e => evaluate(p.precondition, {}, sessionContext.timestamp, (e) =>
session.configContextCallback(sessionContext, e) session.configContextCallback(sessionContext, e)
) )
) { ) {
@@ -529,7 +530,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
const fault = { const fault = {
code: "preset_loop", code: "preset_loop",
message: "The presets are stuck in an endless configuration loop", message: "The presets are stuck in an endless configuration loop",
timestamp: sessionContext.timestamp timestamp: sessionContext.timestamp,
}; };
recordFault(sessionContext, fault); recordFault(sessionContext, fault);
// No need to save retryNow // No need to save retryNow
@@ -610,13 +611,13 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
session.clearProvisions(sessionContext); session.clearProvisions(sessionContext);
// Clear expired tasks // Clear expired tasks
sessionContext.tasks = sessionContext.tasks.filter(task => { sessionContext.tasks = sessionContext.tasks.filter((task) => {
if (!(task.expiry <= sessionContext.timestamp)) return true; if (!(task.expiry <= sessionContext.timestamp)) return true;
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "Task expired", message: "Task expired",
task: task task: task,
}); });
if (!sessionContext.doneTasks) sessionContext.doneTasks = []; if (!sessionContext.doneTasks) sessionContext.doneTasks = [];
@@ -633,7 +634,7 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
}); });
const task = sessionContext.tasks.find( const task = sessionContext.tasks.find(
t => !sessionContext.faults[`task_${t._id}`] (t) => !sessionContext.faults[`task_${t._id}`]
); );
if (!task) return applyPresets(sessionContext); if (!task) return applyPresets(sessionContext);
@@ -646,7 +647,7 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
sessionContext.channels[`task_${task._id}`] = 0; sessionContext.channels[`task_${task._id}`] = 0;
for (const p of task.parameterNames) { for (const p of task.parameterNames) {
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["refresh", p] ["refresh", p],
]); ]);
} }
@@ -656,14 +657,14 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
sessionContext.channels[`task_${task._id}`] = 0; sessionContext.channels[`task_${task._id}`] = 0;
for (const p of task.parameterValues) { for (const p of task.parameterValues) {
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["value", p[0], p[1]] ["value", p[0], p[1]],
]); ]);
} }
break; break;
case "refreshObject": case "refreshObject":
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["refresh", task.objectName] ["refresh", task.objectName],
]); ]);
break; break;
case "reboot": case "reboot":
@@ -674,20 +675,20 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
break; break;
case "download": case "download":
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["download", task.fileType, task.fileName, task.targetFileName || ""] ["download", task.fileType, task.fileName, task.targetFileName || ""],
]); ]);
break; break;
case "addObject": case "addObject":
alias = (task.parameterValues || []) alias = (task.parameterValues || [])
.map(p => `${p[0]}:${JSON.stringify(p[1])}`) .map((p) => `${p[0]}:${JSON.stringify(p[1])}`)
.join(","); .join(",");
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["instances", `${task.objectName}.[${alias}]`, "+1"] ["instances", `${task.objectName}.[${alias}]`, "+1"],
]); ]);
break; break;
case "deleteObject": case "deleteObject":
session.addProvisions(sessionContext, `task_${task._id}`, [ session.addProvisions(sessionContext, `task_${task._id}`, [
["instances", task.objectName, 0] ["instances", task.objectName, 0],
]); ]);
break; break;
default: default:
@@ -799,13 +800,13 @@ async function sendAcsRequest(
const rpc = { const rpc = {
id: id, id: id,
acsRequest: acsRequest, acsRequest: acsRequest,
cwmpVersion: sessionContext.cwmpVersion cwmpVersion: sessionContext.cwmpVersion,
}; };
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "ACS request", message: "ACS request",
rpc: rpc rpc: rpc,
}); });
const res = soap.response(rpc); const res = soap.response(rpc);
@@ -821,7 +822,7 @@ async function getSession(connection, sessionId): Promise<SessionContext> {
if (!sessionId) return null; if (!sessionId) return null;
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
const sessionContextString = await cache.pop(`session_${sessionId}`); const sessionContextString = await cache.pop(`session_${sessionId}`);
if (!sessionContextString) return null; if (!sessionContextString) return null;
@@ -848,7 +849,7 @@ export function onConnection(socket: Socket): void {
const timeoutMsg = logger.flatten({ const timeoutMsg = logger.flatten({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "Session timeout", message: "Session timeout",
sessionTimestamp: sessionContext.timestamp sessionTimestamp: sessionContext.timestamp,
}); });
const timeout = const timeout =
@@ -883,7 +884,7 @@ setInterval(() => {
droppedRequests: stats.droppedRequests, droppedRequests: stats.droppedRequests,
totalRequests: stats.totalRequests, totalRequests: stats.totalRequests,
initiatedSessions: stats.initiatedSessions, initiatedSessions: stats.initiatedSessions,
pid: process.pid pid: process.pid,
}); });
} }
@@ -908,20 +909,20 @@ async function getDueTasksAndFaultsAndOperations(
tasks: resParsed.tasks || [], tasks: resParsed.tasks || [],
faults: resParsed.faults || {}, faults: resParsed.faults || {},
operations: resParsed.operations || {}, operations: resParsed.operations || {},
ttl: 0 ttl: 0,
}; };
} }
const res2 = await Promise.all([ const res2 = await Promise.all([
db.getDueTasks(deviceId, timestamp), db.getDueTasks(deviceId, timestamp),
db.getFaults(deviceId), db.getFaults(deviceId),
db.getOperations(deviceId) db.getOperations(deviceId),
]); ]);
return { return {
tasks: res2[0][0], tasks: res2[0][0],
faults: res2[1], faults: res2[1],
operations: res2[2], operations: res2[2],
ttl: res2[0][1] || 0 ttl: res2[0][1] || 0,
}; };
} }
@@ -935,7 +936,7 @@ async function cacheDueTasksAndFaultsAndOperations(
const v = { const v = {
tasks: null, tasks: null,
faults: null, faults: null,
operations: null operations: null,
}; };
if (tasks.length) v.tasks = tasks; if (tasks.length) v.tasks = tasks;
if (Object.keys(faults).length) v.faults = faults; if (Object.keys(faults).length) v.faults = faults;
@@ -955,7 +956,7 @@ async function cacheDueTasksAndFaultsAndOperations(
async function reportBadState(sessionContext: SessionContext): Promise<void> { async function reportBadState(sessionContext: SessionContext): Promise<void> {
logger.accessError({ logger.accessError({
message: "Bad session state", message: "Bad session state",
sessionContext: sessionContext sessionContext: sessionContext,
}); });
const httpResponse = sessionContext.httpResponse; const httpResponse = sessionContext.httpResponse;
currentSessions.delete(httpResponse.connection); currentSessions.delete(httpResponse.connection);
@@ -976,7 +977,7 @@ async function responseUnauthorized(
// Invalid credentials // Invalid credentials
logger.accessError({ logger.accessError({
message: "Authentication failure", message: "Authentication failure",
sessionContext: sessionContext sessionContext: sessionContext,
}); });
resHeaders["Connection"] = "close"; resHeaders["Connection"] = "close";
@@ -1007,7 +1008,7 @@ async function responseUnauthorized(
async function processRequest( async function processRequest(
sessionContext: SessionContext, sessionContext: SessionContext,
rpc: SoapMessage, rpc: SoapMessage,
parseWarnings: any[], parseWarnings: Record<string, unknown>[],
body: string body: string
): Promise<void> { ): Promise<void> {
for (const w of parseWarnings) { for (const w of parseWarnings) {
@@ -1027,7 +1028,7 @@ async function processRequest(
"cwmp.debug", "cwmp.debug",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
e => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e)
); );
if (!sessionContext.timeout) { if (!sessionContext.timeout) {
@@ -1036,7 +1037,7 @@ async function processRequest(
"cwmp.sessionTimeout", "cwmp.sessionTimeout",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
e => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e)
); );
} }
@@ -1064,7 +1065,7 @@ async function processRequest(
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "Inform", message: "Inform",
rpc: rpc rpc: rpc,
}); });
return writeResponse(sessionContext, res); return writeResponse(sessionContext, res);
@@ -1099,7 +1100,7 @@ async function processRequest(
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "CPE request", message: "CPE request",
rpc: rpc rpc: rpc,
}); });
return transferComplete(sessionContext, rpc); return transferComplete(sessionContext, rpc);
} else if (rpc.cpeRequest.name === "GetRPCMethods") { } else if (rpc.cpeRequest.name === "GetRPCMethods") {
@@ -1108,15 +1109,15 @@ async function processRequest(
logger.accessInfo({ logger.accessInfo({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "CPE request", message: "CPE request",
rpc: rpc rpc: rpc,
}); });
const res = soap.response({ const res = soap.response({
id: rpc.id, id: rpc.id,
acsResponse: { acsResponse: {
name: "GetRPCMethodsResponse", name: "GetRPCMethodsResponse",
methodList: ["Inform", "GetRPCMethods", "TransferComplete"] methodList: ["Inform", "GetRPCMethods", "TransferComplete"],
}, } as AcsResponse,
cwmpVersion: sessionContext.cwmpVersion cwmpVersion: sessionContext.cwmpVersion,
}); });
return writeResponse(sessionContext, res); return writeResponse(sessionContext, res);
} else { } else {
@@ -1136,7 +1137,7 @@ async function processRequest(
logger.accessWarn({ logger.accessWarn({
sessionContext: sessionContext, sessionContext: sessionContext,
message: "CPE fault", message: "CPE fault",
rpc: rpc rpc: rpc,
}); });
const fault = await session.rpcFault(sessionContext, rpc.id, rpc.cpeFault); const fault = await session.rpcFault(sessionContext, rpc.id, rpc.cpeFault);
@@ -1179,7 +1180,7 @@ export function listener(
.then(() => { .then(() => {
stats.concurrentRequests -= 1; stats.concurrentRequests -= 1;
}) })
.catch(err => { .catch((err) => {
currentSessions.delete(httpResponse.connection); currentSessions.delete(httpResponse.connection);
httpResponse.writeHead(500, { Connection: "close" }); httpResponse.writeHead(500, { Connection: "close" });
httpResponse.end(`${err.name}: ${err.message}`); httpResponse.end(`${err.name}: ${err.message}`);
@@ -1208,7 +1209,7 @@ async function listenerAsync(
if (httpRequest.method !== "POST") { if (httpRequest.method !== "POST") {
httpResponse.writeHead(405, { httpResponse.writeHead(405, {
Allow: "POST", Allow: "POST",
Connection: "close" Connection: "close",
}); });
httpResponse.end("405 Method Not Allowed"); httpResponse.end("405 Method Not Allowed");
return; return;
@@ -1225,7 +1226,7 @@ async function listenerAsync(
if (!sessionId && stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) { if (!sessionId && stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
httpResponse.writeHead(503, { httpResponse.writeHead(503, {
"Retry-after": 60, "Retry-after": 60,
Connection: "close" Connection: "close",
}); });
httpResponse.end("503 Service Unavailable"); httpResponse.end("503 Service Unavailable");
stats.droppedRequests += 1; stats.droppedRequests += 1;
@@ -1252,7 +1253,7 @@ async function listenerAsync(
const chunks: Buffer[] = []; const chunks: Buffer[] = [];
let bytes = 0; let bytes = 0;
stream.on("data", chunk => { stream.on("data", (chunk) => {
chunks.push(chunk); chunks.push(chunk);
bytes += chunk.length; bytes += chunk.length;
}); });
@@ -1290,7 +1291,7 @@ async function listenerAsync(
) { ) {
logger.accessError({ logger.accessError({
message: "Invalid session", message: "Invalid session",
sessionContext: sessionContext sessionContext: sessionContext,
}); });
const _body = "Invalid session"; const _body = "Invalid session";
@@ -1327,7 +1328,7 @@ async function listenerAsync(
if (!charset) { if (!charset) {
const parse = parseXmlDeclaration(body); const parse = parseXmlDeclaration(body);
const e = parse ? parse.find(s => s.localName === "encoding") : null; const e = parse ? parse.find((s) => s.localName === "encoding") : null;
charset = e ? e.value.toLowerCase() : "utf8"; charset = e ? e.value.toLowerCase() : "utf8";
} }
@@ -1340,8 +1341,8 @@ async function listenerAsync(
parseError: msg, parseError: msg,
sessionContext: sessionContext || { sessionContext: sessionContext || {
httpRequest: httpRequest, httpRequest: httpRequest,
httpResponse: httpResponse httpResponse: httpResponse,
} },
}); });
httpResponse.setHeader("Content-Length", Buffer.byteLength(msg)); httpResponse.setHeader("Content-Length", Buffer.byteLength(msg));
httpResponse.writeHead(400, { Connection: "close" }); httpResponse.writeHead(400, { Connection: "close" });
@@ -1354,10 +1355,10 @@ async function listenerAsync(
cacheSnapshot, cacheSnapshot,
"cwmp.debug", "cwmp.debug",
{ {
remoteAddress: getRequestOrigin(httpRequest).remoteAddress remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
}, },
Date.now(), Date.now(),
e => { (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress; return getRequestOrigin(httpRequest).remoteAddress;
return e; return e;
@@ -1383,8 +1384,8 @@ async function listenerAsync(
parseError: error.message.trim(), parseError: error.message.trim(),
sessionContext: sessionContext || { sessionContext: sessionContext || {
httpRequest: httpRequest, httpRequest: httpRequest,
httpResponse: httpResponse httpResponse: httpResponse,
} },
}); });
httpResponse.setHeader("Content-Length", Buffer.byteLength(error.message)); httpResponse.setHeader("Content-Length", Buffer.byteLength(error.message));
httpResponse.writeHead(400, { Connection: "close" }); httpResponse.writeHead(400, { Connection: "close" });
@@ -1402,10 +1403,10 @@ async function listenerAsync(
cacheSnapshot, cacheSnapshot,
"cwmp.debug", "cwmp.debug",
{ {
remoteAddress: getRequestOrigin(httpRequest).remoteAddress remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
}, },
Date.now(), Date.now(),
e => { (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress; return getRequestOrigin(httpRequest).remoteAddress;
return e; return e;
@@ -1425,8 +1426,8 @@ async function listenerAsync(
message: "Invalid session", message: "Invalid session",
sessionContext: sessionContext || { sessionContext: sessionContext || {
httpRequest: httpRequest, httpRequest: httpRequest,
httpResponse: httpResponse httpResponse: httpResponse,
} },
}); });
const _body = "Invalid session"; const _body = "Invalid session";
httpResponse.setHeader("Content-Length", Buffer.byteLength(_body)); httpResponse.setHeader("Content-Length", Buffer.byteLength(_body));
@@ -1436,10 +1437,10 @@ async function listenerAsync(
cacheSnapshot, cacheSnapshot,
"cwmp.debug", "cwmp.debug",
{ {
remoteAddress: getRequestOrigin(httpRequest).remoteAddress remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
}, },
Date.now(), Date.now(),
e => { (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress; return getRequestOrigin(httpRequest).remoteAddress;
return e; return e;
@@ -1473,7 +1474,7 @@ async function listenerAsync(
tasks: dueTasks, tasks: dueTasks,
faults, faults,
operations, operations,
ttl: cacheUntil ttl: cacheUntil,
} = await getDueTasksAndFaultsAndOperations( } = await getDueTasksAndFaultsAndOperations(
deviceId, deviceId,
_sessionContext.timestamp _sessionContext.timestamp
+75 -62
View File
@@ -27,7 +27,7 @@ import {
SessionFault, SessionFault,
Task, Task,
Operation, Operation,
Expression Expression,
} from "./types"; } from "./types";
import Path from "./common/path"; import Path from "./common/path";
@@ -49,7 +49,7 @@ export let client: MongoClient;
export async function connect(): Promise<void> { export async function connect(): Promise<void> {
clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), { clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), {
useNewUrlParser: true useNewUrlParser: true,
}); });
client = await clientPromise; client = await clientPromise;
@@ -77,7 +77,7 @@ export async function disconnect(): Promise<void> {
// Optimize projection by removing overlaps // Optimize projection by removing overlaps
// This can modify the object // This can modify the object
function optimizeProjection(obj: {}): {} { function optimizeProjection(obj: { [path: string]: 1 }): { [path: string]: 1 } {
if (obj[""]) return { "": obj[""] }; if (obj[""]) return { "": obj[""] };
const keys = Object.keys(obj).sort(); const keys = Object.keys(obj).sort();
@@ -104,13 +104,13 @@ export async function fetchDevice(
[ [
Path.parse("Events"), Path.parse("Events"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] },
], ],
[ [
Path.parse("DeviceID"), Path.parse("DeviceID"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] },
] ],
]; ];
const device = await devicesCollection.findOne({ _id: id }); const device = await devicesCollection.findOne({ _id: id });
@@ -157,8 +157,8 @@ export async function fetchDevice(
{ {
object: [+v, 0], object: [+v, 0],
writable: [+v, 0], writable: [+v, 0],
value: [+v, [+v, "xsd:dateTime"]] value: [+v, [+v, "xsd:dateTime"]],
} },
]); ]);
break; break;
case "_lastBoot": case "_lastBoot":
@@ -168,8 +168,8 @@ export async function fetchDevice(
{ {
object: [+v, 0], object: [+v, 0],
writable: [+v, 0], writable: [+v, 0],
value: [+v, [+v, "xsd:dateTime"]] value: [+v, [+v, "xsd:dateTime"]],
} },
]); ]);
break; break;
case "_lastBootstrap": case "_lastBootstrap":
@@ -179,8 +179,8 @@ export async function fetchDevice(
{ {
object: [+v, 0], object: [+v, 0],
writable: [+v, 0], writable: [+v, 0],
value: [+v, [+v, "xsd:dateTime"]] value: [+v, [+v, "xsd:dateTime"]],
} },
]); ]);
break; break;
case "_registered": case "_registered":
@@ -191,8 +191,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [+v, "xsd:dateTime"]] value: [timestamp, [+v, "xsd:dateTime"]],
} },
]); ]);
break; break;
case "_id": case "_id":
@@ -202,8 +202,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v as string, "xsd:string"]] value: [timestamp, [v as string, "xsd:string"]],
} },
]); ]);
break; break;
case "_tags": case "_tags":
@@ -211,7 +211,7 @@ export async function fetchDevice(
res.push([ res.push([
Path.parse("Tags"), Path.parse("Tags"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] },
]); ]);
} }
@@ -223,8 +223,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 1], writable: [timestamp, 1],
value: [timestamp, [true, "xsd:boolean"]] value: [timestamp, [true, "xsd:boolean"]],
} },
]); ]);
} }
break; break;
@@ -236,8 +236,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v["_Manufacturer"], "xsd:string"]] value: [timestamp, [v["_Manufacturer"], "xsd:string"]],
} },
]); ]);
} }
@@ -248,8 +248,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v["_OUI"], "xsd:string"]] value: [timestamp, [v["_OUI"], "xsd:string"]],
} },
]); ]);
} }
@@ -260,8 +260,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v["_ProductClass"], "xsd:string"]] value: [timestamp, [v["_ProductClass"], "xsd:string"]],
} },
]); ]);
} }
@@ -272,8 +272,8 @@ export async function fetchDevice(
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v["_SerialNumber"], "xsd:string"]] value: [timestamp, [v["_SerialNumber"], "xsd:string"]],
} },
]); ]);
} }
} }
@@ -398,7 +398,7 @@ export async function saveDevice(
} else { } else {
if (!update["$pull"]["_tags"]) { if (!update["$pull"]["_tags"]) {
update["$pull"]["_tags"] = { update["$pull"]["_tags"] = {
$in: [] $in: [],
}; };
} }
update["$pull"]["_tags"]["$in"].push(path.segments[1]); update["$pull"]["_tags"]["$in"].push(path.segments[1]);
@@ -510,7 +510,7 @@ export async function saveDevice(
} }
const result = await devicesCollection.updateOne({ _id: deviceId }, update, { const result = await devicesCollection.updateOne({ _id: deviceId }, update, {
upsert: isNew upsert: isNew,
}); });
if (result.result.n !== 1) if (result.result.n !== 1)
@@ -523,7 +523,7 @@ export async function saveDevice(
} }
export async function getFaults( export async function getFaults(
deviceId deviceId: string
): Promise<{ [channel: string]: SessionFault }> { ): Promise<{ [channel: string]: SessionFault }> {
const res = await faultsCollection const res = await faultsCollection
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } }) .find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
@@ -543,24 +543,31 @@ export async function getFaults(
return faults; return faults;
} }
export async function saveFault(deviceId, channel, fault): Promise<void> { export async function saveFault(
deviceId: string,
channel: string,
fault: SessionFault
): Promise<void> {
const id = `${deviceId}:${channel}`; const id = `${deviceId}:${channel}`;
fault = Object.assign({}, fault); const f = Object.assign({}, fault) as Record<string, any>;
fault._id = id; f["_id"] = id;
fault.device = deviceId; f["device"] = deviceId;
fault.channel = channel; f["channel"] = channel;
fault.timestamp = new Date(fault.timestamp); f["timestamp"] = new Date(fault.timestamp);
fault.provisions = JSON.stringify(fault.provisions); f["provisions"] = JSON.stringify(fault.provisions);
await faultsCollection.replaceOne({ _id: id }, fault, { upsert: true }); await faultsCollection.replaceOne({ _id: id }, f, { upsert: true });
} }
export async function deleteFault(deviceId, channel): Promise<void> { export async function deleteFault(
deviceId: string,
channel: string
): Promise<void> {
await faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` }); await faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` });
} }
export async function getDueTasks( export async function getDueTasks(
deviceId, deviceId: string,
timestamp timestamp: number
): Promise<[Task[], number]> { ): Promise<[Task[], number]> {
const cur = tasksCollection.find({ device: deviceId }).sort(["timestamp"]); const cur = tasksCollection.find({ device: deviceId }).sort(["timestamp"]);
const tasks = [] as Task[]; const tasks = [] as Task[];
@@ -593,14 +600,17 @@ export async function getDueTasks(
return [tasks, null]; return [tasks, null];
} }
export async function clearTasks(deviceId, taskIds): Promise<void> { export async function clearTasks(
deviceId: string,
taskIds: string[]
): Promise<void> {
await tasksCollection.deleteMany({ await tasksCollection.deleteMany({
_id: { $in: taskIds.map(id => new ObjectID(id)) } _id: { $in: taskIds.map((id) => new ObjectID(id)) },
}); });
} }
export async function getOperations( export async function getOperations(
deviceId deviceId: string
): Promise<{ [commandKey: string]: Operation }> { ): Promise<{ [commandKey: string]: Operation }> {
const res = await operationsCollection const res = await operationsCollection
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } }) .find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
@@ -620,43 +630,46 @@ export async function getOperations(
} }
export async function saveOperation( export async function saveOperation(
deviceId, deviceId: string,
commandKey, commandKey: string,
operation operation: Operation
): Promise<void> { ): Promise<void> {
const id = `${deviceId}:${commandKey}`; const id = `${deviceId}:${commandKey}`;
operation = Object.assign({}, operation); const o = Object.assign({}, operation) as Record<string, any>;
operation._id = id; o["_id"] = id;
operation.timestamp = new Date(operation.timestamp); o["timestamp"] = new Date(operation.timestamp);
operation.provisions = JSON.stringify(operation.provisions); o["provisions"] = JSON.stringify(operation.provisions);
operation.retries = JSON.stringify(operation.retries); o["retries"] = JSON.stringify(operation.retries);
operation.args = JSON.stringify(operation.args); o["args"] = JSON.stringify(operation.args);
await operationsCollection.replaceOne({ _id: id }, operation, { await operationsCollection.replaceOne({ _id: id }, operation, {
upsert: true upsert: true,
}); });
} }
export async function deleteOperation(deviceId, commandKey): Promise<void> { export async function deleteOperation(
deviceId: string,
commandKey: string
): Promise<void> {
await operationsCollection.deleteOne({ _id: `${deviceId}:${commandKey}` }); await operationsCollection.deleteOne({ _id: `${deviceId}:${commandKey}` });
} }
export async function getPresets(): Promise<{}[]> { export async function getPresets(): Promise<Record<string, any>[]> {
return presetsCollection.find().toArray(); return presetsCollection.find().toArray();
} }
export async function getObjects(): Promise<{}[]> { export async function getObjects(): Promise<Record<string, any>[]> {
return objectsCollection.find().toArray(); return objectsCollection.find().toArray();
} }
export async function getProvisions(): Promise<{}[]> { export async function getProvisions(): Promise<Record<string, any>[]> {
return provisionsCollection.find().toArray(); return provisionsCollection.find().toArray();
} }
export async function getVirtualParameters(): Promise<{}[]> { export async function getVirtualParameters(): Promise<Record<string, any>[]> {
return virtualParametersCollection.find().toArray(); return virtualParametersCollection.find().toArray();
} }
export function getFiles(): Promise<{}[]> { export function getFiles(): Promise<Record<string, any>[]> {
return filesCollection.find().toArray(); return filesCollection.find().toArray();
} }
@@ -664,9 +677,9 @@ export async function getConfig(): Promise<
{ id: string; value: Expression }[] { id: string; value: Expression }[]
> { > {
const res = await configCollection.find().toArray(); const res = await configCollection.find().toArray();
return res.map(c => ({ return res.map((c) => ({
id: c["_id"], id: c["_id"],
value: parse(c["value"]) value: parse(c["value"]),
})); }));
} }
+7 -7
View File
@@ -21,7 +21,7 @@ import {
IncomingMessage, IncomingMessage,
ServerResponse, ServerResponse,
ClientRequest, ClientRequest,
RequestOptions RequestOptions,
} from "http"; } from "http";
import { Socket } from "net"; import { Socket } from "net";
import { appendFileSync } from "fs"; import { appendFileSync } from "fs";
@@ -60,7 +60,7 @@ export function incomingHttpRequest(
method: httpRequest.method, method: httpRequest.method,
url: httpRequest.url, url: httpRequest.url,
headers: httpRequest.headers, headers: httpRequest.headers,
body: body body: body,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
@@ -86,7 +86,7 @@ export function outgoingHttpResponse(
connection: getConnectionTimestamp(con), connection: getConnectionTimestamp(con),
statusCode: httpResponse.statusCode, statusCode: httpResponse.statusCode,
headers: httpResponse.getHeaders(), headers: httpResponse.getHeaders(),
body: body body: body,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
@@ -115,7 +115,7 @@ export function outgoingHttpRequest(
method: options.method || "GET", method: options.method || "GET",
url: options.path, url: options.path,
headers: httpRequest.getHeaders(), headers: httpRequest.getHeaders(),
body: body body: body,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
@@ -143,7 +143,7 @@ export function outgoingHttpRequestError(
method: options.method, method: options.method,
url: options.path, url: options.path,
headers: httpRequest.getHeaders(), headers: httpRequest.getHeaders(),
error: err.message error: err.message,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
@@ -169,7 +169,7 @@ export function incomingHttpResponse(
connection: getConnectionTimestamp(httpResponse.connection), connection: getConnectionTimestamp(httpResponse.connection),
statusCode: httpResponse.statusCode, statusCode: httpResponse.statusCode,
headers: httpResponse.headers, headers: httpResponse.headers,
body: body body: body,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
@@ -193,7 +193,7 @@ export function outgoingUdpMessage(
remoteAddress: remoteAddress, remoteAddress: remoteAddress,
deviceId: deviceId, deviceId: deviceId,
remotePort: remotePort, remotePort: remotePort,
body: body body: body,
}; };
if (DEBUG_FORMAT === "yaml") if (DEBUG_FORMAT === "yaml")
+47 -22
View File
@@ -21,10 +21,15 @@ import Path from "./common/path";
import * as config from "./config"; import * as config from "./config";
import * as device from "./device"; import * as device from "./device";
import * as scheduling from "./scheduling"; import * as scheduling from "./scheduling";
import { SessionContext, Declaration } from "./types";
const MAX_DEPTH = +config.get("MAX_DEPTH"); const MAX_DEPTH = +config.get("MAX_DEPTH");
export function refresh(sessionContext, provision, declarations): boolean { export function refresh(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if ( if (
(provision.length !== 2 || typeof provision[1] !== "string") && (provision.length !== 2 || typeof provision[1] !== "string") &&
(provision.length !== 3 || (provision.length !== 3 ||
@@ -38,7 +43,7 @@ export function refresh(sessionContext, provision, declarations): boolean {
segments.length = MAX_DEPTH; segments.length = MAX_DEPTH;
segments.fill("*", l); segments.fill("*", l);
const path = Path.parse(segments.join(".")); const path = Path.parse(segments.join("."));
const every = 1000 * (provision[2] || 1); const every = 1000 * ((provision[2] as number) || 1);
const offset = scheduling.variance(sessionContext.deviceId, every); const offset = scheduling.variance(sessionContext.deviceId, every);
const t = scheduling.interval(sessionContext.timestamp, every, offset); const t = scheduling.interval(sessionContext.timestamp, every, offset);
@@ -49,14 +54,18 @@ export function refresh(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { object: 1, writable: 1, value: t }, attrGet: { object: 1, writable: 1, value: t },
attrSet: null, attrSet: null,
defer: true defer: true,
}); });
} }
return true; return true;
} }
export function value(sessionContext, provision, declarations): boolean { export function value(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if (provision.length !== 3 || typeof provision[1] !== "string") if (provision.length !== 3 || typeof provision[1] !== "string")
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
@@ -66,13 +75,17 @@ export function value(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: { value: [provision[2]] }, attrSet: { value: [provision[2]] },
defer: true defer: true,
}); });
return true; return true;
} }
export function tag(sessionContext, provision, declarations): boolean { export function tag(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if ( if (
provision.length !== 3 || provision.length !== 3 ||
typeof provision[1] !== "string" || typeof provision[1] !== "string" ||
@@ -86,13 +99,17 @@ export function tag(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: { value: [provision[2]] }, attrSet: { value: [provision[2]] },
defer: true defer: true,
}); });
return true; return true;
} }
export function reboot(sessionContext, provision, declarations): boolean { export function reboot(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if (provision.length !== 1) throw new Error("Invalid arguments"); if (provision.length !== 1) throw new Error("Invalid arguments");
declarations.push({ declarations.push({
@@ -101,13 +118,17 @@ export function reboot(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: { value: [sessionContext.timestamp] }, attrSet: { value: [sessionContext.timestamp] },
defer: true defer: true,
}); });
return true; return true;
} }
export function reset(sessionContext, provision, declarations): boolean { export function reset(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if (provision.length !== 1) throw new Error("Invalid arguments"); if (provision.length !== 1) throw new Error("Invalid arguments");
declarations.push({ declarations.push({
@@ -116,13 +137,17 @@ export function reset(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: { value: [sessionContext.timestamp] }, attrSet: { value: [sessionContext.timestamp] },
defer: true defer: true,
}); });
return true; return true;
} }
export function download(sessionContext, provision, declarations): boolean { export function download(
sessionContext: SessionContext,
provision: (string | number | boolean)[],
declarations: Declaration[]
): boolean {
if ( if (
(provision.length !== 3 || (provision.length !== 3 ||
typeof provision[1] !== "string" || typeof provision[1] !== "string" ||
@@ -137,7 +162,7 @@ export function download(sessionContext, provision, declarations): boolean {
const alias = [ const alias = [
`FileType:${JSON.stringify(provision[1] || "")}`, `FileType:${JSON.stringify(provision[1] || "")}`,
`FileName:${JSON.stringify(provision[2] || "")}`, `FileName:${JSON.stringify(provision[2] || "")}`,
`TargetFileName:${JSON.stringify(provision[3] || "")}` `TargetFileName:${JSON.stringify(provision[3] || "")}`,
].join(","); ].join(",");
declarations.push({ declarations.push({
@@ -146,7 +171,7 @@ export function download(sessionContext, provision, declarations): boolean {
pathSet: 1, pathSet: 1,
attrGet: null, attrGet: null,
attrSet: null, attrSet: null,
defer: true defer: true,
}); });
declarations.push({ declarations.push({
@@ -155,18 +180,18 @@ export function download(sessionContext, provision, declarations): boolean {
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
attrSet: { value: [sessionContext.timestamp] }, attrSet: { value: [sessionContext.timestamp] },
defer: true defer: true,
}); });
return true; return true;
} }
export function instances( export function instances(
sessionContext, sessionContext: SessionContext,
provision, provision: (string | number | boolean)[],
declarations, declarations: Declaration[],
startRevision, startRevision: number,
endRevision endRevision: number
): boolean { ): boolean {
if (provision.length !== 3 || typeof provision[1] !== "string") if (provision.length !== 3 || typeof provision[1] !== "string")
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
@@ -184,7 +209,7 @@ export function instances(
pathSet: null, pathSet: null,
attrGet: null, attrGet: null,
attrSet: null, attrSet: null,
defer: true defer: true,
}); });
if (endRevision === startRevision) return false; if (endRevision === startRevision) return false;
@@ -203,7 +228,7 @@ export function instances(
pathSet: count, pathSet: count,
attrGet: null, attrGet: null,
attrSet: null, attrSet: null,
defer: true defer: true,
}); });
return true; return true;
+24 -15
View File
@@ -18,12 +18,18 @@
*/ */
import Path from "./common/path"; import Path from "./common/path";
import { DeviceData, Declaration, Clear } from "./types"; import {
DeviceData,
Declaration,
Clear,
AttributeTimestamps,
Attributes,
} from "./types";
const CHANGE_FLAGS = { const CHANGE_FLAGS = {
object: 2, object: 2,
writable: 4, writable: 4,
value: 8 value: 8,
}; };
function parseBool(v): boolean { function parseBool(v): boolean {
@@ -36,7 +42,7 @@ function parseBool(v): boolean {
export function sanitizeParameterValue( export function sanitizeParameterValue(
parameterValue: [string | number | boolean, string] parameterValue: [string | number | boolean, string]
): [string | number | boolean, string?] { ): [string | number | boolean, string] {
if (parameterValue[0] != null) { if (parameterValue[0] != null) {
switch (parameterValue[1]) { switch (parameterValue[1]) {
case "xsd:boolean": case "xsd:boolean":
@@ -96,8 +102,8 @@ export function getAliasDeclarations(
pathSet: null, pathSet: null,
attrGet: attrGet, attrGet: attrGet,
attrSet: null, attrSet: null,
defer: true defer: true,
} },
]; ];
if (path.alias) { if (path.alias) {
@@ -107,7 +113,7 @@ export function getAliasDeclarations(
for (const [p] of alias as [Path, string][]) { for (const [p] of alias as [Path, string][]) {
decs = decs.concat( decs = decs.concat(
getAliasDeclarations(parent.concat(p), timestamp, { getAliasDeclarations(parent.concat(p), timestamp, {
value: timestamp value: timestamp,
}) })
); );
} }
@@ -189,10 +195,10 @@ export function unpack(
} }
export function clear( export function clear(
deviceData, deviceData: DeviceData,
path, path: Path,
timestamp, timestamp: number,
attributes, attributes: AttributeTimestamps,
changeFlags = 0 changeFlags = 0
): void { ): void {
const changeTrackers = {}; const changeTrackers = {};
@@ -271,8 +277,8 @@ function compareEquality(a, b): boolean {
export function set( export function set(
deviceData: DeviceData, deviceData: DeviceData,
path: Path, path: Path,
timestamp, timestamp: number,
attributes, attributes: Attributes,
toClear?: Clear[] toClear?: Clear[]
): Clear[] { ): Clear[] {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
@@ -300,7 +306,7 @@ export function set(
attributes.object[1] && attributes.object[1] &&
attributes.object[0] >= (attributes.value ? attributes.value[0] : 0) attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)
) )
attributes.value = [attributes.object[0]]; attributes.value = [attributes.object[0], null];
const newAttributes = Object.assign({}, currentAttributes, attributes); const newAttributes = Object.assign({}, currentAttributes, attributes);
@@ -376,7 +382,7 @@ export function track(
deviceData: DeviceData, deviceData: DeviceData,
path: Path, path: Path,
marker: string, marker: string,
attributes? attributes?: string[]
): void { ): void {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
let f = 1; let f = 1;
@@ -393,7 +399,10 @@ export function track(
cur[marker] |= f; cur[marker] |= f;
} }
export function clearTrackers(deviceData: DeviceData, tracker): void { export function clearTrackers(
deviceData: DeviceData,
tracker: string | string[]
): void {
if (Array.isArray(tracker)) { if (Array.isArray(tracker)) {
for (const v of deviceData.trackers.values()) for (const v of deviceData.trackers.values())
for (const t of tracker) delete v[t]; for (const t of tracker) delete v[t];
+7 -7
View File
@@ -36,8 +36,8 @@ function messageHandler(message): void {
} }
} }
export function run(args): Promise<{ fault: Fault; value: any }> { export function run(args: string[]): Promise<{ fault: Fault; value: any }> {
return new Promise(resolve => { return new Promise((resolve) => {
const scriptName = args[0]; const scriptName = args[0];
const id = crypto.randomBytes(8).toString("hex"); const id = crypto.randomBytes(8).toString("hex");
@@ -45,16 +45,16 @@ export function run(args): Promise<{ fault: Fault; value: any }> {
if (!processes[scriptName]) { if (!processes[scriptName]) {
const p = spawn(ROOT_DIR + "/bin/genieacs-ext", [scriptName], { const p = spawn(ROOT_DIR + "/bin/genieacs-ext", [scriptName], {
stdio: ["inherit", "inherit", "inherit", "ipc"] stdio: ["inherit", "inherit", "inherit", "ipc"],
}); });
processes[scriptName] = p; processes[scriptName] = p;
p.on("error", err => { p.on("error", (err) => {
if (processes[scriptName] === p) { if (processes[scriptName] === p) {
if (jobs.delete(id)) { if (jobs.delete(id)) {
resolve({ resolve({
fault: { code: err.name, message: err.message }, fault: { code: err.name, message: err.message },
value: null value: null,
}); });
} }
@@ -74,7 +74,7 @@ export function run(args): Promise<{ fault: Fault; value: any }> {
if (jobs.delete(id)) { if (jobs.delete(id)) {
resolve({ resolve({
fault: { code: "timeout", message: "Extension timed out" }, fault: { code: "timeout", message: "Extension timed out" },
value: null value: null,
}); });
} }
}, TIMEOUT); }, TIMEOUT);
@@ -86,7 +86,7 @@ export function run(args): Promise<{ fault: Fault; value: any }> {
} }
function kill(process: ChildProcess): Promise<void> { function kill(process: ChildProcess): Promise<void> {
return new Promise(resolve => { return new Promise((resolve) => {
const timeToKill = Date.now() + 5000; const timeToKill = Date.now() + 5000;
process.kill(); process.kill();
+3 -3
View File
@@ -35,7 +35,7 @@ const FORWARDED_HEADER = "" + config.get("FORWARDED_HEADER");
const cache = new WeakMap<IncomingMessage, RequestOrigin>(); const cache = new WeakMap<IncomingMessage, RequestOrigin>();
const cidrs: [IPv4 | IPv6, number][] = []; const cidrs: [IPv4 | IPv6, number][] = [];
for (const str of FORWARDED_HEADER.split(",").map(s => s.trim())) { for (const str of FORWARDED_HEADER.split(",").map((s) => s.trim())) {
try { try {
cidrs.push(parseCIDR(str)); cidrs.push(parseCIDR(str));
} catch (err) { } catch (err) {
@@ -104,13 +104,13 @@ export function getRequestOrigin(request: IncomingMessage): RequestOrigin {
remoteAddress: soc.remoteAddress, remoteAddress: soc.remoteAddress,
remotePort: soc.remotePort, remotePort: soc.remotePort,
host: request.headers["host"], host: request.headers["host"],
encrypted: !!(request.socket as TLSSocket).encrypted encrypted: !!(request.socket as TLSSocket).encrypted,
}; };
const header = request.headers["forwarded"]; const header = request.headers["forwarded"];
if (header) { if (header) {
const ip = parse(soc.remoteAddress) as IPv4; const ip = parse(soc.remoteAddress) as IPv4;
if (cidrs.some(cidr => ip.match(cidr as [IPv4, number]))) { if (cidrs.some((cidr) => ip.match(cidr as [IPv4, number]))) {
const parsed = parseForwardedHeader(header); const parsed = parseForwardedHeader(header);
if (parsed["proto"] === "https") { if (parsed["proto"] === "https") {
+2 -2
View File
@@ -36,7 +36,7 @@ export function listener(
const log = { const log = {
message: "Fetch file", message: "Fetch file",
filename: filename, filename: filename,
remoteAddress: getRequestOrigin(request).remoteAddress remoteAddress: getRequestOrigin(request).remoteAddress,
}; };
db.filesCollection.findOne({ _id: filename }, (err, file) => { db.filesCollection.findOne({ _id: filename }, (err, file) => {
@@ -52,7 +52,7 @@ export function listener(
response.writeHead(200, { response.writeHead(200, {
"Content-Type": file.contentType || "application/octet-stream", "Content-Type": file.contentType || "application/octet-stream",
"Content-Length": file.length "Content-Length": file.length,
}); });
const bucket = new GridFSBucket(db.client.db()); const bucket = new GridFSBucket(db.client.db());
+1 -1
View File
@@ -31,7 +31,7 @@ const UNDISCOVERED_DEPTH = 7;
// point has been discovered. // point has been discovered.
export function estimateGpnCount( export function estimateGpnCount(
gpnPatterns: [Path, number][], gpnPatterns: [Path, number][],
depth: number = 0 depth = 0
): number { ): number {
const children: { [segment: string]: [Path, number][] } = {}; const children: { [segment: string]: [Path, number][] } = {};
const wildcardChildren: [Path, number][] = []; const wildcardChildren: [Path, number][] = [];
+50 -49
View File
@@ -95,11 +95,11 @@ export async function getStatus(): Promise<Status> {
filters: !Object.keys(ui["filters"]).length, filters: !Object.keys(ui["filters"]).length,
device: !Object.keys(ui["device"]).length, device: !Object.keys(ui["device"]).length,
index: !Object.keys(ui["index"]).length, index: !Object.keys(ui["index"]).length,
overview: !Object.keys(ui["overview"]).length overview: !Object.keys(ui["overview"]).length,
}; };
} }
export async function seed(options): Promise<void> { export async function seed(options: Record<string, boolean>): Promise<void> {
const resources = {}; const resources = {};
const proms = []; const proms = [];
@@ -117,12 +117,12 @@ export async function seed(options): Promise<void> {
role: "admin", role: "admin",
resource: "virtualParameters", resource: "virtualParameters",
access: 3, access: 3,
validate: "true" validate: "true",
} },
]; ];
resources["users"] = [ resources["users"] = [
{ username: "admin", password: "admin", roles: ["admin"] } { username: "admin", password: "admin", roles: ["admin"] },
]; ];
} }
@@ -135,7 +135,7 @@ export async function seed(options): Promise<void> {
{ _id: "ui.filters.1.parameter", value: "DeviceID.ProductClass" }, { _id: "ui.filters.1.parameter", value: "DeviceID.ProductClass" },
{ _id: "ui.filters.1.type", value: "'string'" }, { _id: "ui.filters.1.type", value: "'string'" },
{ _id: "ui.filters.2.label", value: "'Tag'" }, { _id: "ui.filters.2.label", value: "'Tag'" },
{ _id: "ui.filters.2.type", value: "'tag'" } { _id: "ui.filters.2.type", value: "'tag'" },
]); ]);
} }
@@ -149,118 +149,118 @@ export async function seed(options): Promise<void> {
{ _id: "ui.device.2.parameters.0.label", value: "'Last inform'" }, { _id: "ui.device.2.parameters.0.label", value: "'Last inform'" },
{ {
_id: "ui.device.2.parameters.0.components.0.type", _id: "ui.device.2.parameters.0.components.0.type",
value: "'parameter'" value: "'parameter'",
}, },
{ _id: "ui.device.2.parameters.0.components.1.chart", value: "'online'" }, { _id: "ui.device.2.parameters.0.components.1.chart", value: "'online'" },
{ {
_id: "ui.device.2.parameters.0.components.1.type", _id: "ui.device.2.parameters.0.components.1.type",
value: "'overview-dot'" value: "'overview-dot'",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.type", _id: "ui.device.2.parameters.0.components.2.type",
value: "'summon-button'" value: "'summon-button'",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.0", _id: "ui.device.2.parameters.0.components.2.parameters.0",
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion" value: "InternetGatewayDevice.DeviceInfo.HardwareVersion",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.1", _id: "ui.device.2.parameters.0.components.2.parameters.1",
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion" value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.2", _id: "ui.device.2.parameters.0.components.2.parameters.2",
value: value:
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress" "InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.3", _id: "ui.device.2.parameters.0.components.2.parameters.3",
value: value:
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress" "InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.4", _id: "ui.device.2.parameters.0.components.2.parameters.4",
value: "InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID" value: "InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.5", _id: "ui.device.2.parameters.0.components.2.parameters.5",
value: value:
"InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase" "InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.6", _id: "ui.device.2.parameters.0.components.2.parameters.6",
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName" value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.7", _id: "ui.device.2.parameters.0.components.2.parameters.7",
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress" value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress",
}, },
{ {
_id: "ui.device.2.parameters.0.components.2.parameters.8", _id: "ui.device.2.parameters.0.components.2.parameters.8",
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress" value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress",
}, },
{ {
_id: "ui.device.2.parameters.0.parameter", _id: "ui.device.2.parameters.0.parameter",
value: "DATE_STRING(Events.Inform)" value: "DATE_STRING(Events.Inform)",
}, },
{ _id: "ui.device.2.parameters.1.label", value: "'Serial number'" }, { _id: "ui.device.2.parameters.1.label", value: "'Serial number'" },
{ {
_id: "ui.device.2.parameters.1.parameter", _id: "ui.device.2.parameters.1.parameter",
value: "DeviceID.SerialNumber" value: "DeviceID.SerialNumber",
}, },
{ _id: "ui.device.2.parameters.2.label", value: "'Product class'" }, { _id: "ui.device.2.parameters.2.label", value: "'Product class'" },
{ {
_id: "ui.device.2.parameters.2.parameter", _id: "ui.device.2.parameters.2.parameter",
value: "DeviceID.ProductClass" value: "DeviceID.ProductClass",
}, },
{ _id: "ui.device.2.parameters.3.label", value: "'OUI'" }, { _id: "ui.device.2.parameters.3.label", value: "'OUI'" },
{ {
_id: "ui.device.2.parameters.3.parameter", _id: "ui.device.2.parameters.3.parameter",
value: "DeviceID.OUI" value: "DeviceID.OUI",
}, },
{ _id: "ui.device.2.parameters.4.label", value: "'Manufacturer'" }, { _id: "ui.device.2.parameters.4.label", value: "'Manufacturer'" },
{ {
_id: "ui.device.2.parameters.4.parameter", _id: "ui.device.2.parameters.4.parameter",
value: "DeviceID.Manufacturer" value: "DeviceID.Manufacturer",
}, },
{ _id: "ui.device.2.parameters.5.label", value: "'Hardware version'" }, { _id: "ui.device.2.parameters.5.label", value: "'Hardware version'" },
{ {
_id: "ui.device.2.parameters.5.parameter", _id: "ui.device.2.parameters.5.parameter",
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion" value: "InternetGatewayDevice.DeviceInfo.HardwareVersion",
}, },
{ _id: "ui.device.2.parameters.6.label", value: "'Software version'" }, { _id: "ui.device.2.parameters.6.label", value: "'Software version'" },
{ {
_id: "ui.device.2.parameters.6.parameter", _id: "ui.device.2.parameters.6.parameter",
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion" value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
}, },
{ _id: "ui.device.2.parameters.7.label", value: "'MAC'" }, { _id: "ui.device.2.parameters.7.label", value: "'MAC'" },
{ {
_id: "ui.device.2.parameters.7.parameter", _id: "ui.device.2.parameters.7.parameter",
value: value:
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress" "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress",
}, },
{ _id: "ui.device.2.parameters.8.label", value: "'IP'" }, { _id: "ui.device.2.parameters.8.label", value: "'IP'" },
{ {
_id: "ui.device.2.parameters.8.parameter", _id: "ui.device.2.parameters.8.parameter",
value: value:
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress" "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
}, },
{ _id: "ui.device.2.parameters.9.label", value: "'WLAN SSID'" }, { _id: "ui.device.2.parameters.9.label", value: "'WLAN SSID'" },
{ {
_id: "ui.device.2.parameters.9.parameter", _id: "ui.device.2.parameters.9.parameter",
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID" value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
}, },
{ _id: "ui.device.2.parameters.10.label", value: "'WLAN passphrase'" }, { _id: "ui.device.2.parameters.10.label", value: "'WLAN passphrase'" },
{ {
_id: "ui.device.2.parameters.10.parameter", _id: "ui.device.2.parameters.10.parameter",
value: value:
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase" "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase",
}, },
{ _id: "ui.device.3.type", value: "'parameter-table'" }, { _id: "ui.device.3.type", value: "'parameter-table'" },
{ _id: "ui.device.3.label", value: "'WLAN hosts'" }, { _id: "ui.device.3.label", value: "'WLAN hosts'" },
{ {
_id: "ui.device.3.parameter", _id: "ui.device.3.parameter",
value: "InternetGatewayDevice.LANDevice.1.Hosts.Host" value: "InternetGatewayDevice.LANDevice.1.Hosts.Host",
}, },
{ _id: "ui.device.3.childParameters.0.label", value: "'Host name'" }, { _id: "ui.device.3.childParameters.0.label", value: "'Host name'" },
{ _id: "ui.device.3.childParameters.0.parameter", value: "HostName" }, { _id: "ui.device.3.childParameters.0.parameter", value: "HostName" },
@@ -278,12 +278,12 @@ export async function seed(options): Promise<void> {
{ _id: "ui.device.5.element", value: "'div.container-full-width'" }, { _id: "ui.device.5.element", value: "'div.container-full-width'" },
{ {
_id: "ui.device.5.components.0.components.0", _id: "ui.device.5.components.0.components.0",
value: "'All parameters'" value: "'All parameters'",
}, },
{ _id: "ui.device.5.components.0.element", value: "'h3'" }, { _id: "ui.device.5.components.0.element", value: "'h3'" },
{ _id: "ui.device.5.components.0.type", value: "'container'" }, { _id: "ui.device.5.components.0.type", value: "'container'" },
{ _id: "ui.device.5.components.1.type", value: "'all-parameters'" }, { _id: "ui.device.5.components.1.type", value: "'all-parameters'" },
{ _id: "ui.device.6.type", value: "'device-actions'" } { _id: "ui.device.6.type", value: "'device-actions'" },
]); ]);
} }
@@ -298,18 +298,18 @@ export async function seed(options): Promise<void> {
{ _id: "ui.index.2.label", value: "'Software version'" }, { _id: "ui.index.2.label", value: "'Software version'" },
{ {
_id: "ui.index.2.parameter", _id: "ui.index.2.parameter",
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion" value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
}, },
{ _id: "ui.index.3.label", value: "'IP'" }, { _id: "ui.index.3.label", value: "'IP'" },
{ {
_id: "ui.index.3.parameter", _id: "ui.index.3.parameter",
value: value:
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress" "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
}, },
{ _id: "ui.index.4.label", value: "'SSID'" }, { _id: "ui.index.4.label", value: "'SSID'" },
{ {
_id: "ui.index.4.parameter", _id: "ui.index.4.parameter",
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID" value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
}, },
{ _id: "ui.index.5.type", value: "'container'" }, { _id: "ui.index.5.type", value: "'container'" },
{ _id: "ui.index.5.label", value: "'Last inform'" }, { _id: "ui.index.5.label", value: "'Last inform'" },
@@ -322,7 +322,7 @@ export async function seed(options): Promise<void> {
{ _id: "ui.index.6.label", value: "'Tags'" }, { _id: "ui.index.6.label", value: "'Tags'" },
{ _id: "ui.index.6.parameter", value: "Tags" }, { _id: "ui.index.6.parameter", value: "Tags" },
{ _id: "ui.index.6.unsortable", value: "true" }, { _id: "ui.index.6.unsortable", value: "true" },
{ _id: "ui.index.6.writable", value: "false" } { _id: "ui.index.6.writable", value: "false" },
]); ]);
} }
@@ -331,43 +331,44 @@ export async function seed(options): Promise<void> {
{ _id: "ui.overview.charts.online.label", value: "'Online status'" }, { _id: "ui.overview.charts.online.label", value: "'Online status'" },
{ {
_id: "ui.overview.charts.online.slices.1_onlineNow.color", _id: "ui.overview.charts.online.slices.1_onlineNow.color",
value: "'#31a354'" value: "'#31a354'",
}, },
{ {
_id: "ui.overview.charts.online.slices.1_onlineNow.filter", _id: "ui.overview.charts.online.slices.1_onlineNow.filter",
value: "Events.Inform > NOW() - 5 * 60 * 1000" value: "Events.Inform > NOW() - 5 * 60 * 1000",
}, },
{ {
_id: "ui.overview.charts.online.slices.1_onlineNow.label", _id: "ui.overview.charts.online.slices.1_onlineNow.label",
value: "'Online now'" value: "'Online now'",
}, },
{ {
_id: "ui.overview.charts.online.slices.2_past24.color", _id: "ui.overview.charts.online.slices.2_past24.color",
value: "'#a1d99b'" value: "'#a1d99b'",
}, },
{ {
_id: "ui.overview.charts.online.slices.2_past24.filter", _id: "ui.overview.charts.online.slices.2_past24.filter",
value: value:
"Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)" "Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)",
}, },
{ {
_id: "ui.overview.charts.online.slices.2_past24.label", _id: "ui.overview.charts.online.slices.2_past24.label",
value: "'Past 24 hours'" value: "'Past 24 hours'",
}, },
{ {
_id: "ui.overview.charts.online.slices.3_others.color", _id: "ui.overview.charts.online.slices.3_others.color",
value: "'#e5f5e0'" value: "'#e5f5e0'",
}, },
{ {
_id: "ui.overview.charts.online.slices.3_others.filter", _id: "ui.overview.charts.online.slices.3_others.filter",
value: "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)" value:
"Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)",
}, },
{ {
_id: "ui.overview.charts.online.slices.3_others.label", _id: "ui.overview.charts.online.slices.3_others.label",
value: "'Others'" value: "'Others'",
}, },
{ _id: "ui.overview.groups.online.label", value: "''" }, { _id: "ui.overview.groups.online.label", value: "''" },
{ _id: "ui.overview.groups.online.charts.0", value: "'online'" } { _id: "ui.overview.groups.online.charts.0", value: "'online'" },
]); ]);
} }
@@ -378,16 +379,16 @@ export async function seed(options): Promise<void> {
weight: 0, weight: 0,
channel: "bootstrap", channel: "bootstrap",
events: "0 BOOTSTRAP", events: "0 BOOTSTRAP",
provision: "bootstrap" provision: "bootstrap",
}, },
{ _id: "default", weight: 0, channel: "default", provision: "default" }, { _id: "default", weight: 0, channel: "default", provision: "default" },
{ _id: "inform", weight: 0, channel: "inform", provision: "inform" } { _id: "inform", weight: 0, channel: "inform", provision: "inform" },
]; ];
resources["provisions"] = [ resources["provisions"] = [
{ _id: "bootstrap", script: BOOTSTRAP_SCRIPT }, { _id: "bootstrap", script: BOOTSTRAP_SCRIPT },
{ _id: "default", script: DEFAULT_SCRIPT }, { _id: "default", script: DEFAULT_SCRIPT },
{ _id: "inform", script: INFORM_SCRIPT } { _id: "inform", script: INFORM_SCRIPT },
]; ];
} }
+41 -33
View File
@@ -36,7 +36,7 @@ import {
Users, Users,
Permissions, Permissions,
Config, Config,
UiConfig UiConfig,
} from "./types"; } from "./types";
interface Snapshot { interface Snapshot {
@@ -107,11 +107,15 @@ function computeHash(snapshot): string {
return h.digest("hex"); return h.digest("hex");
} }
function flattenObject(src, prefix = "", dst = {}): {} { function flattenObject(
src: Record<string, unknown>,
prefix = "",
dst = {}
): Record<string, unknown> {
for (const k of Object.keys(src)) { for (const k of Object.keys(src)) {
const v = src[k]; const v = src[k];
if (typeof v === "object" && !Array.isArray(v)) if (typeof v === "object" && !Array.isArray(v))
flattenObject(v, `${prefix}${k}.`, dst); flattenObject(v as Record<string, unknown>, `${prefix}${k}.`, dst);
else dst[`${prefix}${k}`] = v; else dst[`${prefix}${k}`] = v;
} }
return dst; return dst;
@@ -121,14 +125,14 @@ async function fetchPresets(): Promise<Preset[]> {
const res = await db.getPresets(); const res = await db.getPresets();
let objects = await db.getObjects(); let objects = await db.getObjects();
objects = objects.map(obj => { objects = objects.map((obj) => {
// Flatten object // Flatten object
obj = flattenObject(obj); obj = flattenObject(obj);
// If no keys are defined, consider all parameters as keys to keep the // If no keys are defined, consider all parameters as keys to keep the
// same behavior from v1.0 // same behavior from v1.0
if (!obj["_keys"] || !obj["_keys"].length) if (!obj["_keys"] || !obj["_keys"].length)
obj["_keys"] = Object.keys(obj).filter(k => !k.startsWith("_")); obj["_keys"] = Object.keys(obj).filter((k) => !k.startsWith("_"));
return obj; return obj;
}); });
@@ -145,12 +149,9 @@ async function fetchPresets(): Promise<Preset[]> {
if (preset["schedule"]) { if (preset["schedule"]) {
const parts = preset["schedule"].trim().split(/\s+/); const parts = preset["schedule"].trim().split(/\s+/);
schedule = { schedule = {
md5: crypto md5: crypto.createHash("md5").update(preset["schedule"]).digest("hex"),
.createHash("md5")
.update(preset["schedule"])
.digest("hex"),
duration: null, duration: null,
schedule: null schedule: null,
}; };
try { try {
@@ -160,7 +161,7 @@ async function fetchPresets(): Promise<Preset[]> {
logger.warn({ logger.warn({
message: "Invalid preset schedule", message: "Invalid preset schedule",
preset: preset["_id"], preset: preset["_id"],
schedule: preset["schedule"] schedule: preset["schedule"],
}); });
schedule.schedule = false; schedule.schedule = false;
} }
@@ -208,7 +209,7 @@ async function fetchPresets(): Promise<Preset[]> {
for (const obj of objects) { for (const obj of objects) {
if (obj["_id"] === c.object) { if (obj["_id"] === c.object) {
const alias = obj["_keys"] const alias = obj["_keys"]
.map(k => `${k}:${JSON.stringify(obj[k])}`) .map((k) => `${k}:${JSON.stringify(obj[k])}`)
.join(","); .join(",");
const p = `${c.name}.[${alias}]`; const p = `${c.name}.[${alias}]`;
_provisions.push(["instances", p, 1]); _provisions.push(["instances", p, 1]);
@@ -226,7 +227,7 @@ async function fetchPresets(): Promise<Preset[]> {
for (const obj of objects) { for (const obj of objects) {
if (obj["_id"] === c.object) { if (obj["_id"] === c.object) {
const alias = obj["_keys"] const alias = obj["_keys"]
.map(k => `${k}:${JSON.stringify(obj[k])}`) .map((k) => `${k}:${JSON.stringify(obj[k])}`)
.join(","); .join(",");
const p = `${c.name}.[${alias}]`; const p = `${c.name}.[${alias}]`;
_provisions.push(["instances", p, 0]); _provisions.push(["instances", p, 0]);
@@ -246,7 +247,7 @@ async function fetchPresets(): Promise<Preset[]> {
schedule: schedule, schedule: schedule,
events: events, events: events,
precondition: precondition, precondition: precondition,
provisions: _provisions provisions: _provisions,
}); });
} }
@@ -263,7 +264,9 @@ async function fetchProvisions(): Promise<Provisions> {
.createHash("md5") .createHash("md5")
.update(r["script"]) .update(r["script"])
.digest("hex"); .digest("hex");
provisions[r["_id"]].script = new vm.Script( provisions[
r["_id"]
].script = new vm.Script(
`"use strict";(function(){\n${r["script"]}\n})();`, `"use strict";(function(){\n${r["script"]}\n})();`,
{ filename: r["_id"], lineOffset: -1, timeout: 50 } { filename: r["_id"], lineOffset: -1, timeout: 50 }
); );
@@ -282,7 +285,9 @@ async function fetchVirtualParameters(): Promise<VirtualParameters> {
.createHash("md5") .createHash("md5")
.update(r["script"]) .update(r["script"])
.digest("hex"); .digest("hex");
virtualParameters[r["_id"]].script = new vm.Script( virtualParameters[
r["_id"]
].script = new vm.Script(
`"use strict";(function(){\n${r["script"]}\n})();`, `"use strict";(function(){\n${r["script"]}\n})();`,
{ filename: r["_id"], lineOffset: -1, timeout: 50 } { filename: r["_id"], lineOffset: -1, timeout: 50 }
); );
@@ -301,7 +306,7 @@ async function fetchPermissions(): Promise<Permissions> {
permissions[p.role][p.access][p.resource] = { permissions[p.role][p.access][p.resource] = {
access: p.access, access: p.access,
filter: parse(p.filter || "true") filter: parse(p.filter || "true"),
}; };
if (p.validate) if (p.validate)
permissions[p.role][p.access][p.resource].validate = parse(p.validate); permissions[p.role][p.access][p.resource].validate = parse(p.validate);
@@ -333,7 +338,7 @@ async function fetchUsers(): Promise<Users> {
users[user._id] = { users[user._id] = {
password: user.password, password: user.password,
salt: user.salt, salt: user.salt,
roles: user.roles.split(",").map(s => s.trim()) roles: user.roles.split(",").map((s) => s.trim()),
}; };
} }
@@ -349,7 +354,7 @@ async function fetchConfig(): Promise<[Config, UiConfig]> {
filters: {}, filters: {},
device: {}, device: {},
index: {}, index: {},
overview: {} overview: {},
}; };
const _config = {}; const _config = {};
@@ -376,7 +381,7 @@ async function fetchConfig(): Promise<[Config, UiConfig]> {
async function refresh(): Promise<void> { async function refresh(): Promise<void> {
if (!nextRefresh) { if (!nextRefresh) {
await new Promise(resolve => setTimeout(resolve, 20)); await new Promise((resolve) => setTimeout(resolve, 20));
await refresh(); await refresh();
return; return;
} }
@@ -400,7 +405,7 @@ async function refresh(): Promise<void> {
fetchFiles(), fetchFiles(),
fetchPermissions(), fetchPermissions(),
fetchUsers(), fetchUsers(),
fetchConfig() fetchConfig(),
]); ]);
const snapshot = { const snapshot = {
@@ -411,7 +416,7 @@ async function refresh(): Promise<void> {
permissions: res[4], permissions: res[4],
users: res[5], users: res[5],
config: res[6][0], config: res[6][0],
ui: res[6][1] ui: res[6][1],
}; };
if (currentSnapshot) { if (currentSnapshot) {
@@ -435,29 +440,29 @@ export async function getCurrentSnapshot(): Promise<string> {
return currentSnapshot; return currentSnapshot;
} }
export function hasSnapshot(hash): boolean { export function hasSnapshot(hash: string): boolean {
return snapshots.has(hash); return snapshots.has(hash);
} }
export function getPresets(snapshotKey): Preset[] { export function getPresets(snapshotKey: string): Preset[] {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.presets; return snapshot.presets;
} }
export function getProvisions(snapshotKey): Provisions { export function getProvisions(snapshotKey: string): Provisions {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.provisions; return snapshot.provisions;
} }
export function getVirtualParameters(snapshotKey): VirtualParameters { export function getVirtualParameters(snapshotKey: string): VirtualParameters {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.virtualParameters; return snapshot.virtualParameters;
} }
export function getFiles(snapshotKey): Files { export function getFiles(snapshotKey: string): Files {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.files; return snapshot.files;
@@ -466,7 +471,7 @@ export function getFiles(snapshotKey): Files {
export function getConfig( export function getConfig(
snapshotKey: string, snapshotKey: string,
key: string, key: string,
context: {}, context: Record<string, unknown>,
now: number, now: number,
cb?: (Expression) => Expression cb?: (Expression) => Expression
): string | number | boolean | null { ): string | number | boolean | null {
@@ -488,7 +493,7 @@ export function getConfig(
"CONNECTION_REQUEST_ALLOW_BASIC_AUTH", "CONNECTION_REQUEST_ALLOW_BASIC_AUTH",
"cwmp.maxCommitIterations": "MAX_COMMIT_ITERATIONS", "cwmp.maxCommitIterations": "MAX_COMMIT_ITERATIONS",
"cwmp.deviceOnlineThreshold": "DEVICE_ONLINE_THRESHOLD", "cwmp.deviceOnlineThreshold": "DEVICE_ONLINE_THRESHOLD",
"cwmp.udpConnectionRequestPort": "UDP_CONNECTION_REQUEST_PORT" "cwmp.udpConnectionRequestPort": "UDP_CONNECTION_REQUEST_PORT",
}; };
if (!(key in snapshot.config)) { if (!(key in snapshot.config)) {
@@ -509,28 +514,31 @@ export function getConfig(
return Array.isArray(v) ? null : v; return Array.isArray(v) ? null : v;
} }
export function getConfigExpression(snapshotKey, key): Expression { export function getConfigExpression(
snapshotKey: string,
key: string
): Expression {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.config[key]; return snapshot.config[key];
} }
export function getUsers(snapshotKey): {} { export function getUsers(snapshotKey: string): Users {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.users; return snapshot.users;
} }
export function getPermissions(snapshotKey): Permissions { export function getPermissions(snapshotKey: string): Permissions {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.permissions; return snapshot.permissions;
} }
export function getUiConfig(snapshotKey): UiConfig { export function getUiConfig(snapshotKey: string): UiConfig {
const snapshot = snapshots.get(snapshotKey); const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
+74 -45
View File
@@ -22,6 +22,14 @@ import * as os from "os";
import * as config from "./config"; import * as config from "./config";
import { getRequestOrigin } from "./forwarded"; import { getRequestOrigin } from "./forwarded";
import {
SessionContext,
AcsRequest,
CpeRequest,
CpeFault,
InformRequest,
Fault,
} from "./types";
const REOPEN_EVERY = 60000; const REOPEN_EVERY = 60000;
@@ -67,7 +75,7 @@ function reopen(): void {
if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) { if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) {
logStream.end(); logStream.end();
logStream = fs.createWriteStream(null, { logStream = fs.createWriteStream(null, {
fd: fs.openSync(LOG_FILE, "a") fd: fs.openSync(LOG_FILE, "a"),
}); });
logStat = fs.fstatSync(logStream.fd); logStat = fs.fstatSync(logStream.fd);
} }
@@ -91,7 +99,7 @@ function reopen(): void {
) { ) {
accessLogStream.end(); accessLogStream.end();
accessLogStream = fs.createWriteStream(null, { accessLogStream = fs.createWriteStream(null, {
fd: fs.openSync(ACCESS_LOG_FILE, "a") fd: fs.openSync(ACCESS_LOG_FILE, "a"),
}); });
accessLogStat = fs.fstatSync(accessLogStream.fd); accessLogStat = fs.fstatSync(accessLogStream.fd);
} }
@@ -105,7 +113,7 @@ function reopen(): void {
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref(); setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
} }
export function init(service, version): void { export function init(service: string, version: string): void {
defaultMeta.hostname = os.hostname(); defaultMeta.hostname = os.hostname();
defaultMeta.pid = process.pid; defaultMeta.pid = process.pid;
defaultMeta.name = `genieacs-${service}`; defaultMeta.name = `genieacs-${service}`;
@@ -121,7 +129,7 @@ export function init(service, version): void {
if (ACCESS_LOG_FILE) { if (ACCESS_LOG_FILE) {
accessLogStream = fs.createWriteStream(null, { accessLogStream = fs.createWriteStream(null, {
fd: fs.openSync(ACCESS_LOG_FILE, "a") fd: fs.openSync(ACCESS_LOG_FILE, "a"),
}); });
accessLogStat = fs.fstatSync(accessLogStream.fd); accessLogStat = fs.fstatSync(accessLogStream.fd);
} }
@@ -147,62 +155,75 @@ export function close(): void {
logStream.end(); logStream.end();
} }
export function flatten(details): {} { export function flatten(
details: Record<string, unknown>
): Record<string, unknown> {
if (details.sessionContext) { if (details.sessionContext) {
details.deviceId = details.sessionContext.deviceId; const sessionContext = details.sessionContext as SessionContext;
details.deviceId = sessionContext.deviceId;
details.remoteAddress = getRequestOrigin( details.remoteAddress = getRequestOrigin(
details.sessionContext.httpRequest sessionContext.httpRequest
).remoteAddress; ).remoteAddress;
delete details.sessionContext; delete details.sessionContext;
} }
if (details.exception) { if (details.exception) {
details.exceptionName = details.exception.name; const err = details.exception as Error;
details.exceptionMessage = details.exception.message; details.exceptionName = err.name;
details.exceptionStack = details.exception.stack; details.exceptionMessage = err.message;
details.exceptionStack = err.stack;
delete details.exception; delete details.exception;
} }
if (details.task) { if (details.task) {
details.taskId = details.task._id; details.taskId = details.task["_id"];
delete details.task; delete details.task;
} }
if (details.rpc) { if (details.rpc) {
if (details.rpc.acsRequest) { const rpc = details.rpc as {
details.acsRequestId = details.rpc.id; id: string;
details.acsRequestName = details.rpc.acsRequest.name; acsRequest?: AcsRequest;
if (details.rpc.acsRequest.commandKey) cpeRequest?: CpeRequest;
details.acsRequestCommandKey = details.rpc.acsRequest.commandKey; cpeFault?: CpeFault;
} else if (details.rpc.cpeRequest) { };
details.cpeRequestId = details.rpc.id; if (rpc.acsRequest) {
if (details.rpc.cpeRequest.name === "Inform") { details.acsRequestId = rpc.id;
details.informEvent = details.rpc.cpeRequest.event.join(","); details.acsRequestName = rpc.acsRequest.name;
details.informRetryCount = details.rpc.cpeRequest.retryCount; if (rpc.acsRequest["commandKey"])
details.acsRequestCommandKey = rpc.acsRequest["commandKey"];
} else if (rpc.cpeRequest) {
details.cpeRequestId = rpc.id;
if (rpc.cpeRequest.name === "Inform") {
details.informEvent = (rpc.cpeRequest as InformRequest).event.join(",");
details.informRetryCount = (rpc.cpeRequest as InformRequest).retryCount;
} else { } else {
details.cpeRequestName = details.rpc.cpeRequest.name; details.cpeRequestName = rpc.cpeRequest.name;
if (details.rpc.cpeRequest.commandKey) if (rpc.cpeRequest["commandKey"])
details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey; details.cpeRequestCommandKey = rpc.cpeRequest["commandKey"];
} }
} else if (details.rpc.cpeFault) { } else if (rpc.cpeFault) {
details.acsRequestId = details.rpc.id; details.acsRequestId = rpc.id;
details.cpeFaultCode = details.rpc.cpeFault.detail.faultCode; details.cpeFaultCode = rpc.cpeFault.detail.faultCode;
details.cpeFaultString = details.rpc.cpeFault.detail.faultString; details.cpeFaultString = rpc.cpeFault.detail.faultString;
} }
delete details.rpc; delete details.rpc;
} }
if (details.fault) { if (details.fault) {
details.faultCode = details.fault.code; const fault = details.fault as Fault;
details.faultMessage = details.fault.message; details.faultCode = fault.code;
details.faultMessage = fault.message;
delete details.fault; delete details.fault;
} }
// For genieacs-ui // For genieacs-ui
if (details.context) { if (details.context) {
details.remoteAddress = getRequestOrigin(details.context.req).remoteAddress; details.remoteAddress = getRequestOrigin(
if (details.context.state.user) details.context["req"]
details.user = details.context.state.user.username; ).remoteAddress;
if (details.context["state"].user)
details.user = details.context["state"].user.username;
delete details.context; delete details.context;
} }
@@ -212,7 +233,10 @@ export function flatten(details): {} {
return details; return details;
} }
function formatJson(details, systemd): string { function formatJson(
details: Record<string, unknown>,
systemd: boolean
): string {
if (systemd) { if (systemd) {
let severity = ""; let severity = "";
if (details.severity === "info") severity = "<6>"; if (details.severity === "info") severity = "<6>";
@@ -225,14 +249,17 @@ function formatJson(details, systemd): string {
return `${JSON.stringify(flatten(details))}${os.EOL}`; return `${JSON.stringify(flatten(details))}${os.EOL}`;
} }
function formatSimple(details, systemd): string { function formatSimple(
details: Record<string, unknown>,
systemd: boolean
): string {
const skip = { const skip = {
user: true, user: true,
remoteAddress: true, remoteAddress: true,
severity: true, severity: true,
timestamp: true, timestamp: true,
message: true, message: true,
deviceId: !!details.sessionContext deviceId: !!details.sessionContext,
}; };
flatten(details); flatten(details);
@@ -265,12 +292,14 @@ function formatSimple(details, systemd): string {
return `${severity}${remote}${details.message}${meta}${os.EOL}`; return `${severity}${remote}${details.message}${meta}${os.EOL}`;
} }
return `${details.timestamp} [${details.severity.toUpperCase()}] ${remote}${ return `${
details.timestamp
} [${(details.severity as string).toUpperCase()}] ${remote}${
details.message details.message
}${meta}${os.EOL}`; }${meta}${os.EOL}`;
} }
function log(details): void { function log(details: Record<string, unknown>): void {
details.timestamp = new Date().toISOString(); details.timestamp = new Date().toISOString();
if (LOG_FORMAT === "json") { if (LOG_FORMAT === "json") {
details = Object.assign({}, defaultMeta, details); details = Object.assign({}, defaultMeta, details);
@@ -280,22 +309,22 @@ function log(details): void {
} }
} }
export function info(details): void { export function info(details: Record<string, unknown>): void {
details.severity = "info"; details.severity = "info";
log(details); log(details);
} }
export function warn(details): void { export function warn(details: Record<string, unknown>): void {
details.severity = "warn"; details.severity = "warn";
log(details); log(details);
} }
export function error(details): void { export function error(details: Record<string, unknown>): void {
details.severity = "error"; details.severity = "error";
log(details); log(details);
} }
export function accessLog(details): void { export function accessLog(details: Record<string, unknown>): void {
details.timestamp = new Date().toISOString(); details.timestamp = new Date().toISOString();
if (ACCESS_LOG_FORMAT === "json") { if (ACCESS_LOG_FORMAT === "json") {
Object.assign(details, defaultMeta); Object.assign(details, defaultMeta);
@@ -305,17 +334,17 @@ export function accessLog(details): void {
} }
} }
export function accessInfo(details): void { export function accessInfo(details: Record<string, unknown>): void {
details.severity = "info"; details.severity = "info";
accessLog(details); accessLog(details);
} }
export function accessWarn(details): void { export function accessWarn(details: Record<string, unknown>): void {
details.severity = "warn"; details.severity = "warn";
accessLog(details); accessLog(details);
} }
export function accessError(details): void { export function accessError(details: Record<string, unknown>): void {
details.severity = "error"; details.severity = "error";
accessLog(details); accessLog(details);
} }
+67 -55
View File
@@ -24,8 +24,8 @@ import { Expression, Fault, Task } from "./types";
const isArray = Array.isArray; const isArray = Array.isArray;
export function processDeviceFilter(filter): Expression { export function processDeviceFilter(filter: Expression): Expression {
return map(filter, exp => { return map(filter, (exp) => {
if (!isArray(exp)) return exp; if (!isArray(exp)) return exp;
if (exp[0] === "PARAM") { if (exp[0] === "PARAM") {
@@ -61,10 +61,10 @@ export function processDeviceFilter(filter): Expression {
"LIKE", "LIKE",
"NOT LIKE", "NOT LIKE",
"IS NULL", "IS NULL",
"IS NOT NULL" "IS NOT NULL",
].includes(exp[0]) ].includes(exp[0])
) { ) {
let e = map(exp, ee => { let e = map(exp, (ee) => {
if ( if (
isArray(ee) && isArray(ee) &&
ee[0] === "PARAM" && ee[0] === "PARAM" &&
@@ -86,8 +86,8 @@ export function processDeviceFilter(filter): Expression {
}); });
} }
export function processTasksFilter(filter): Expression { export function processTasksFilter(filter: Expression): Expression {
return map(filter, exp => { return map(filter, (exp) => {
if (!isArray(exp)) return exp; if (!isArray(exp)) return exp;
if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) { if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) {
const e = exp.slice(); const e = exp.slice();
@@ -102,8 +102,8 @@ export function processTasksFilter(filter): Expression {
}); });
} }
export function processFaultsFilter(filter): Expression { export function processFaultsFilter(filter: Expression): Expression {
return map(filter, exp => { return map(filter, (exp) => {
if (!isArray(exp)) return exp; if (!isArray(exp)) return exp;
if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) { if (["=", "<>", ">", ">=", "<", "<="].includes(exp[0])) {
const e = exp.slice(); const e = exp.slice();
@@ -116,7 +116,7 @@ export function processFaultsFilter(filter): Expression {
}); });
} }
export function filterToMongoQuery(exp: Expression): {} { export function filterToMongoQuery(exp: Expression): Record<string, unknown> {
const ops = { const ops = {
OR: 0, OR: 0,
AND: 0, AND: 0,
@@ -130,10 +130,10 @@ export function filterToMongoQuery(exp: Expression): {} {
LIKE: 1, LIKE: 1,
"NOT LIKE": 1, "NOT LIKE": 1,
"IS NULL": 1, "IS NULL": 1,
"IS NOT NULL": 1 "IS NOT NULL": 1,
}; };
function recursive(filter, negate, res = {}): {} { function recursive(filter, negate, res = {}): Record<string, unknown> {
const op = filter[0]; const op = filter[0];
if (ops[op] === 0) { if (ops[op] === 0) {
@@ -249,7 +249,9 @@ export function filterToMongoQuery(exp: Expression): {} {
return recursive(_exp, false); return recursive(_exp, false);
} }
export function processDeviceProjection(projection: {}): {} { export function processDeviceProjection(
projection: Record<string, 1>
): Record<string, 1> {
if (!projection) return projection; if (!projection) return projection;
const p = {}; const p = {};
for (const [k, v] of Object.entries(projection)) { for (const [k, v] of Object.entries(projection)) {
@@ -275,7 +277,9 @@ export function processDeviceProjection(projection: {}): {} {
return p; return p;
} }
export function processDeviceSort(sort: {}): {} { export function processDeviceSort(
sort: Record<string, number>
): Record<string, number> {
if (!sort) return sort; if (!sort) return sort;
const s = {}; const s = {};
for (const [k, v] of Object.entries(sort)) { for (const [k, v] of Object.entries(sort)) {
@@ -291,7 +295,7 @@ export function processDeviceSort(sort: {}): {} {
return s; return s;
} }
function parseDate(d): number | string { function parseDate(d: Date): number | string {
const n = +d; const n = +d;
return isNaN(n) ? "" + d : n; return isNaN(n) ? "" + d : n;
} }
@@ -307,45 +311,45 @@ interface FlatDevice {
}; };
} }
export function flattenDevice(device): FlatDevice { export function flattenDevice(device: Record<string, unknown>): FlatDevice {
function recursive(input, root, output, timestamp): void { function recursive(input, root, output, timestamp): void {
for (const [name, tree] of Object.entries(input)) { for (const [name, tree] of Object.entries(input)) {
if (!root) { if (!root) {
if (name === "_lastInform") { if (name === "_lastInform") {
output["Events.Inform"] = { output["Events.Inform"] = {
value: [parseDate(tree), "xsd:dateTime"], value: [parseDate(tree as Date), "xsd:dateTime"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_registered") { } else if (name === "_registered") {
output["Events.Registered"] = { output["Events.Registered"] = {
value: [parseDate(tree), "xsd:dateTime"], value: [parseDate(tree as Date), "xsd:dateTime"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_lastBoot") { } else if (name === "_lastBoot") {
output["Events.1_BOOT"] = { output["Events.1_BOOT"] = {
value: [parseDate(tree), "xsd:dateTime"], value: [parseDate(tree as Date), "xsd:dateTime"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_lastBootstrap") { } else if (name === "_lastBootstrap") {
output["Events.0_BOOTSTRAP"] = { output["Events.0_BOOTSTRAP"] = {
value: [parseDate(tree), "xsd:dateTime"], value: [parseDate(tree as Date), "xsd:dateTime"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_id") { } else if (name === "_id") {
output["DeviceID.ID"] = { output["DeviceID.ID"] = {
@@ -354,7 +358,7 @@ export function flattenDevice(device): FlatDevice {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_deviceId") { } else if (name === "_deviceId") {
output["DeviceID.Manufacturer"] = { output["DeviceID.Manufacturer"] = {
@@ -363,7 +367,7 @@ export function flattenDevice(device): FlatDevice {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
output["DeviceID.OUI"] = { output["DeviceID.OUI"] = {
value: [tree["_OUI"], "xsd:string"], value: [tree["_OUI"], "xsd:string"],
@@ -371,7 +375,7 @@ export function flattenDevice(device): FlatDevice {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
output["DeviceID.ProductClass"] = { output["DeviceID.ProductClass"] = {
value: [tree["_ProductClass"], "xsd:string"], value: [tree["_ProductClass"], "xsd:string"],
@@ -379,7 +383,7 @@ export function flattenDevice(device): FlatDevice {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
output["DeviceID.SerialNumber"] = { output["DeviceID.SerialNumber"] = {
value: [tree["_SerialNumber"], "xsd:string"], value: [tree["_SerialNumber"], "xsd:string"],
@@ -387,14 +391,14 @@ export function flattenDevice(device): FlatDevice {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} else if (name === "_tags") { } else if (name === "_tags") {
output["Tags"] = { output["Tags"] = {
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: true, object: true,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
for (const t of tree as string[]) { for (const t of tree as string[]) {
@@ -404,7 +408,7 @@ export function flattenDevice(device): FlatDevice {
writable: true, writable: true,
writableTimestamp: timestamp, writableTimestamp: timestamp,
object: false, object: false,
objectTimestamp: timestamp objectTimestamp: timestamp,
}; };
} }
} }
@@ -422,7 +426,7 @@ export function flattenDevice(device): FlatDevice {
if (tree["_value"] != null) { if (tree["_value"] != null) {
attrs["value"] = [ attrs["value"] = [
tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"], tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"],
tree["_type"] tree["_type"],
]; ];
attrs["valueTimestamp"] = +(tree["_timestamp"] || childrenTimestamp); attrs["valueTimestamp"] = +(tree["_timestamp"] || childrenTimestamp);
attrs["object"] = false; attrs["object"] = false;
@@ -446,27 +450,27 @@ export function flattenDevice(device): FlatDevice {
} }
const newDevice = {}; const newDevice = {};
const timestamp = new Date(device["_lastInform"] || 1).getTime(); const timestamp = new Date((device["_lastInform"] as Date) || 1).getTime();
recursive(device, "", newDevice, timestamp); recursive(device, "", newDevice, timestamp);
return newDevice; return newDevice;
} }
export function flattenFault(fault): Fault { export function flattenFault(fault: unknown): Fault {
const f = Object.assign({}, fault); const f = Object.assign({}, fault) as Fault;
if (f.timestamp) f.timestamp = +f.timestamp; if (f.timestamp) f.timestamp = +f.timestamp;
if (f.expiry) f.expiry = +f.expiry; if (f["expiry"]) f["expiry"] = +f["expiry"];
return f; return f as Fault;
} }
export function flattenTask(task): Task { export function flattenTask(task: unknown): Task {
const t = Object.assign({}, task); const t = Object.assign({}, task) as Task;
t._id = "" + t._id; t._id = "" + t._id;
if (t.timestamp) t.timestamp = +t.timestamp; if (t["timestamp"]) t["timestamp"] = +t["timestamp"];
if (t.expiry) t.expiry = +t.expiry; if (t.expiry) t.expiry = +t.expiry;
return t; return t;
} }
export function mongoQueryToFilter(query): Expression { export function mongoQueryToFilter(query: Record<string, unknown>): Expression {
function recursive(_query): Expression { function recursive(_query): Expression {
const expressions: Expression[] = []; const expressions: Expression[] = [];
for (const [k, v] of Object.entries(_query)) { for (const [k, v] of Object.entries(_query)) {
@@ -554,15 +558,19 @@ export function mongoQueryToFilter(query): Expression {
return recursive(query); return recursive(query);
} }
export function flattenPreset(preset): {} { export function flattenPreset(
preset: Record<string, unknown>
): Record<string, unknown> {
const p = Object.assign({}, preset); const p = Object.assign({}, preset);
if (p.precondition) { if (p.precondition) {
try { try {
// Try parse to check expression validity // Try parse to check expression validity
parse(p.precondition); parse(p.precondition as string);
} catch (error) { } catch (error) {
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition)); p.precondition = mongoQueryToFilter(JSON.parse(p.precondition as string));
p.precondition = p.precondition.length ? stringify(p.precondition) : ""; p.precondition = (p.precondition as string).length
? stringify(p.precondition as Expression)
: "";
} }
} }
@@ -574,7 +582,7 @@ export function flattenPreset(preset): {} {
const provision = p.configurations[0]; const provision = p.configurations[0];
if ( if (
p.configurations.length === 1 && (p.configurations as any[]).length === 1 &&
provision.type === "provision" && provision.type === "provision" &&
provision.name && provision.name &&
provision.name.length provision.name.length
@@ -589,30 +597,34 @@ export function flattenPreset(preset): {} {
return p; return p;
} }
export function flattenFile(file): {} { export function flattenFile(
file: Record<string, unknown>
): Record<string, unknown> {
const f = {}; const f = {};
f["_id"] = file["_id"]; f["_id"] = file["_id"];
if (file.metadata) { if (file.metadata) {
f["metadata.fileType"] = file.metadata.fileType || ""; f["metadata.fileType"] = file["metadata"]["fileType"] || "";
f["metadata.oui"] = file.metadata.oui || ""; f["metadata.oui"] = file["metadata"]["oui"] || "";
f["metadata.productClass"] = file.metadata.productClass || ""; f["metadata.productClass"] = file["metadata"]["productClass"] || "";
f["metadata.version"] = file.metadata.version || ""; f["metadata.version"] = file["metadata"]["version"] || "";
} }
return f; return f;
} }
export function preProcessPreset(data): {} { export function preProcessPreset(
data: Record<string, unknown>
): Record<string, unknown> {
const preset = Object.assign({}, data); const preset = Object.assign({}, data);
if (!preset.precondition) preset.precondition = ""; if (!preset.precondition) preset.precondition = "";
// Try parse to check expression validity // Try parse to check expression validity
parse(preset.precondition); parse(preset.precondition as string);
preset.weight = parseInt(preset.weight) || 0; preset.weight = parseInt(preset.weight as string) || 0;
const events = {}; const events = {};
if (preset.events) { if (preset.events) {
for (let e of preset.events.split(",")) { for (let e of (preset.events as string).split(",")) {
let v = true; let v = true;
e = e.trim(); e = e.trim();
if (e.startsWith("-")) { if (e.startsWith("-")) {
@@ -630,7 +642,7 @@ export function preProcessPreset(data): {} {
const configuration = { const configuration = {
type: "provision", type: "provision",
name: preset.provision, name: preset.provision,
args: null args: null,
}; };
if (preset.provisionArgs) if (preset.provisionArgs)
+64 -57
View File
@@ -25,6 +25,7 @@ import * as config from "./config";
import * as db from "./db"; import * as db from "./db";
import * as query from "./query"; import * as query from "./query";
import * as apiFunctions from "./api-functions"; import * as apiFunctions from "./api-functions";
import { IncomingMessage, ServerResponse } from "http";
import * as cache from "./cache"; import * as cache from "./cache";
import { version as VERSION } from "../package.json"; import { version as VERSION } from "../package.json";
import { ping } from "./ping"; import { ping } from "./ping";
@@ -42,7 +43,7 @@ const PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-_%]+)\/?$/;
const VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-_%]+)\/?$/; const VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-_%]+)\/?$/;
const FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-_%:]+)\/?$/; const FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-_%:]+)\/?$/;
function throwError(err, httpResponse?): never { function throwError(err: Error, httpResponse?: ServerResponse): never {
if (httpResponse) { if (httpResponse) {
httpResponse.writeHead(500, { Connection: "close" }); httpResponse.writeHead(500, { Connection: "close" });
httpResponse.end(`${err.name}: ${err.message}`); httpResponse.end(`${err.name}: ${err.message}`);
@@ -50,7 +51,10 @@ function throwError(err, httpResponse?): never {
throw err; throw err;
} }
export function listener(request, response): void { export function listener(
request: IncomingMessage,
response: ServerResponse
): void {
const chunks = []; const chunks = [];
let bytes = 0; let bytes = 0;
response.setHeader("GenieACS-Version", VERSION); response.setHeader("GenieACS-Version", VERSION);
@@ -85,7 +89,7 @@ export function listener(request, response): void {
{ _id: presetName }, { _id: presetName },
preset, preset,
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -94,7 +98,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -102,7 +106,7 @@ export function listener(request, response): void {
} }
); );
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.presetsCollection.deleteOne({ _id: presetName }, err => { db.presetsCollection.deleteOne({ _id: presetName }, (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -111,7 +115,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -132,7 +136,7 @@ export function listener(request, response): void {
{ _id: objectName }, { _id: objectName },
object, object,
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -141,7 +145,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -149,7 +153,7 @@ export function listener(request, response): void {
} }
); );
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.objectsCollection.deleteOne({ _id: objectName }, err => { db.objectsCollection.deleteOne({ _id: objectName }, (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -158,7 +162,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -175,7 +179,7 @@ export function listener(request, response): void {
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
_id: provisionName, _id: provisionName,
script: body.toString() script: body.toString(),
}; };
try { try {
@@ -190,7 +194,7 @@ export function listener(request, response): void {
{ _id: provisionName }, { _id: provisionName },
object, object,
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -199,7 +203,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -207,7 +211,7 @@ export function listener(request, response): void {
} }
); );
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.provisionsCollection.deleteOne({ _id: provisionName }, err => { db.provisionsCollection.deleteOne({ _id: provisionName }, (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -216,7 +220,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -233,7 +237,7 @@ export function listener(request, response): void {
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
_id: virtualParameterName, _id: virtualParameterName,
script: body.toString() script: body.toString(),
}; };
try { try {
@@ -248,7 +252,7 @@ export function listener(request, response): void {
{ _id: virtualParameterName }, { _id: virtualParameterName },
object, object,
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -257,7 +261,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -267,7 +271,7 @@ export function listener(request, response): void {
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.virtualParametersCollection.deleteOne( db.virtualParametersCollection.deleteOne(
{ _id: virtualParameterName }, { _id: virtualParameterName },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -276,7 +280,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -295,7 +299,7 @@ export function listener(request, response): void {
db.devicesCollection.updateOne( db.devicesCollection.updateOne(
{ _id: deviceId }, { _id: deviceId },
{ $addToSet: { _tags: tag } }, { $addToSet: { _tags: tag } },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
@@ -305,7 +309,7 @@ export function listener(request, response): void {
db.devicesCollection.updateOne( db.devicesCollection.updateOne(
{ _id: deviceId }, { _id: deviceId },
{ $pull: { _tags: tag } }, { $pull: { _tags: tag } },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
response.writeHead(200); response.writeHead(200);
@@ -323,12 +327,12 @@ export function listener(request, response): void {
); );
const deviceId = faultId.split(":", 1)[0]; const deviceId = faultId.split(":", 1)[0];
const channel = faultId.slice(deviceId.length + 1); const channel = faultId.slice(deviceId.length + 1);
db.faultsCollection.deleteOne({ _id: faultId }, err => { db.faultsCollection.deleteOne({ _id: faultId }, (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
if (channel.startsWith("task_")) { if (channel.startsWith("task_")) {
const objId = new mongodb.ObjectID(channel.slice(5)); const objId = new mongodb.ObjectID(channel.slice(5));
return void db.tasksCollection.deleteOne({ _id: objId }, err => { return void db.tasksCollection.deleteOne({ _id: objId }, (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -337,7 +341,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -351,7 +355,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -382,17 +386,20 @@ export function listener(request, response): void {
const taskTimeout = const taskTimeout =
(urlParts.query.timeout && (urlParts.query.timeout &&
parseInt(urlParts.query.timeout as string)) || parseInt(urlParts.query.timeout as string)) ||
config.get("DEVICE_ONLINE_THRESHOLD", deviceId); (config.get(
"DEVICE_ONLINE_THRESHOLD",
deviceId
) as number);
apiFunctions apiFunctions
.watchTask(deviceId, task._id, taskTimeout) .watchTask(deviceId, task._id, taskTimeout)
.then(status => { .then((status) => {
if (status === "timeout") { if (status === "timeout") {
response.writeHead( response.writeHead(
202, 202,
"Task queued but not processed", "Task queued but not processed",
{ {
"Content-Type": "application/json" "Content-Type": "application/json",
} }
); );
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
@@ -404,44 +411,44 @@ export function listener(request, response): void {
return void throwError(err, response); return void throwError(err, response);
response.writeHead(202, "Task faulted", { response.writeHead(202, "Task faulted", {
"Content-Type": "application/json" "Content-Type": "application/json",
}); });
response.end(JSON.stringify(task2)); response.end(JSON.stringify(task2));
} }
); );
} else { } else {
response.writeHead(200, { response.writeHead(200, {
"Content-Type": "application/json" "Content-Type": "application/json",
}); });
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
} }
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
}); });
}) })
.catch(err => { .catch((err) => {
response.writeHead(202, err.message, { response.writeHead(202, err.message, {
"Content-Type": "application/json" "Content-Type": "application/json",
}); });
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
}); });
} else { } else {
response.writeHead(202, { response.writeHead(202, {
"Content-Type": "application/json" "Content-Type": "application/json",
}); });
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
} }
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
}); });
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -454,7 +461,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
response.writeHead(504); response.writeHead(504);
response.end(`${err.name}: ${err.message}`); response.end(`${err.name}: ${err.message}`);
}); });
@@ -487,12 +494,12 @@ export function listener(request, response): void {
const deviceId = task.device; const deviceId = task.device;
db.tasksCollection.deleteOne( db.tasksCollection.deleteOne(
{ _id: new mongodb.ObjectID(taskId) }, { _id: new mongodb.ObjectID(taskId) },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
db.faultsCollection.deleteOne( db.faultsCollection.deleteOne(
{ _id: `${deviceId}:task_${taskId}` }, { _id: `${deviceId}:task_${taskId}` },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -501,7 +508,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -527,7 +534,7 @@ export function listener(request, response): void {
const deviceId = task.device; const deviceId = task.device;
db.faultsCollection.deleteOne( db.faultsCollection.deleteOne(
{ _id: `${deviceId}:task_${taskId}` }, { _id: `${deviceId}:task_${taskId}` },
err => { (err) => {
if (err) return void throwError(err, response); if (err) return void throwError(err, response);
cache cache
@@ -536,7 +543,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -562,7 +569,7 @@ export function listener(request, response): void {
fileType: request.headers.filetype, fileType: request.headers.filetype,
oui: request.headers.oui, oui: request.headers.oui,
productClass: request.headers.productclass, productClass: request.headers.productclass,
version: request.headers.version version: request.headers.version,
}; };
const bucket = new mongodb.GridFSBucket(db.client.db()); const bucket = new mongodb.GridFSBucket(db.client.db());
bucket.delete((filename as unknown) as mongodb.ObjectId, () => { bucket.delete((filename as unknown) as mongodb.ObjectId, () => {
@@ -570,11 +577,11 @@ export function listener(request, response): void {
filename, filename,
filename, filename,
{ {
metadata: metadata metadata: metadata,
} }
); );
uploadStream.on("error", err => { uploadStream.on("error", (err) => {
throwError(err, response); throwError(err, response);
}); });
@@ -585,7 +592,7 @@ export function listener(request, response): void {
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
const bucket = new mongodb.GridFSBucket(db.client.db()); const bucket = new mongodb.GridFSBucket(db.client.db());
bucket.delete((filename as unknown) as mongodb.ObjectId, err => { bucket.delete((filename as unknown) as mongodb.ObjectId, (err) => {
if (err) { if (err) {
if (err.message.startsWith("FileNotFound")) { if (err.message.startsWith("FileNotFound")) {
response.writeHead(404); response.writeHead(404);
@@ -618,7 +625,7 @@ export function listener(request, response): void {
response.writeHead(200, { response.writeHead(200, {
"Content-Type": "text/plain", "Content-Type": "text/plain",
"Cache-Control": "no-cache" "Cache-Control": "no-cache",
}); });
response.end(stdout); response.end(stdout);
}); });
@@ -638,7 +645,7 @@ export function listener(request, response): void {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}) })
.catch(err => { .catch((err) => {
setTimeout(() => { setTimeout(() => {
throwError(err, response); throwError(err, response);
}); });
@@ -686,15 +693,15 @@ export function listener(request, response): void {
break; break;
case "tasks": case "tasks":
q = query.sanitizeQueryTypes(q, { q = query.sanitizeQueryTypes(q, {
_id: v => new mongodb.ObjectID(v), _id: (v) => new mongodb.ObjectID(v as string),
timestamp: v => new Date(v), timestamp: (v) => new Date(v as number),
retries: Number retries: Number,
}); });
break; break;
case "faults": case "faults":
q = query.sanitizeQueryTypes(q, { q = query.sanitizeQueryTypes(q, {
timestamp: v => new Date(v), timestamp: (v) => new Date(v as number),
retries: Number retries: Number,
}); });
} }
@@ -730,7 +737,7 @@ export function listener(request, response): void {
response.writeHead(200, { response.writeHead(200, {
"Content-Type": "application/json", "Content-Type": "application/json",
total: total total: total,
}); });
if (request.method === "HEAD") { if (request.method === "HEAD") {
@@ -741,11 +748,11 @@ export function listener(request, response): void {
response.write("[\n"); response.write("[\n");
i = 0; i = 0;
cur.forEach( cur.forEach(
item => { (item) => {
if (i++) response.write(",\n"); if (i++) response.write(",\n");
response.write(JSON.stringify(item)); response.write(JSON.stringify(item));
}, },
err => { (err) => {
if (err) return void throwError(err); if (err) return void throwError(err);
response.end("\n]"); response.end("\n]");
} }
+5 -5
View File
@@ -20,7 +20,7 @@
import { platform } from "os"; import { platform } from "os";
import { exec } from "child_process"; import { exec } from "child_process";
interface Ping { export interface PingResult {
packetsTransmitted: number; packetsTransmitted: number;
packetsReceived: number; packetsReceived: number;
packetLoss: number; packetLoss: number;
@@ -32,7 +32,7 @@ interface Ping {
export function ping( export function ping(
host: string, host: string,
callback: (err, res?, stdout?) => void callback: (err: Error, res?: PingResult, stdout?: string) => void
): void { ): void {
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp; let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
switch (platform()) { switch (platform()) {
@@ -55,7 +55,7 @@ export function ping(
} }
exec(cmd, (err, stdout) => { exec(cmd, (err, stdout) => {
let parsed: Ping; let parsed: PingResult;
if (stdout) { if (stdout) {
const m1 = stdout.match(parseRegExp1); const m1 = stdout.match(parseRegExp1);
if (m1) { if (m1) {
@@ -66,7 +66,7 @@ export function ping(
min: +m1[4], min: +m1[4],
avg: +m1[5], avg: +m1[5],
max: +m1[6], max: +m1[6],
mdev: +m1[7] mdev: +m1[7],
}; };
} else { } else {
const m2 = stdout.match(parseRegExp2); const m2 = stdout.match(parseRegExp2);
@@ -78,7 +78,7 @@ export function ping(
min: null, min: null,
avg: null, avg: null,
max: null, max: null,
mdev: null mdev: null,
}; };
} }
} }
+18 -98
View File
@@ -17,7 +17,9 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import * as common from "./common"; function isObject(obj: any): boolean {
return Object.prototype.toString.call(obj) === "[object Object]";
}
function stringToRegexp(input, flags?): RegExp | false { function stringToRegexp(input, flags?): RegExp | false {
if (input.indexOf("*") === -1) return false; if (input.indexOf("*") === -1) return false;
@@ -34,8 +36,8 @@ function stringToRegexp(input, flags?): RegExp | false {
} }
function normalize(input): any { function normalize(input): any {
if (common.typeOf(input) === common.STRING_TYPE) { if (typeof input === "string") {
const vals = [input]; const vals: any = [input];
const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input); const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
if (m) vals.push({ $regex: new RegExp(m[1], m[2]) }); if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });
@@ -52,14 +54,14 @@ function normalize(input): any {
return input; return input;
} }
export function expandValue(value): any[] { function expandValue(value: unknown): unknown[] {
if (common.typeOf(value) === common.ARRAY_TYPE) { if (Array.isArray(value)) {
let a = []; let a = [];
for (const j of value) a = a.concat(expandValue(j)); for (const j of value) a = a.concat(expandValue(j));
return [a]; return [a];
} else if (common.typeOf(value) !== common.OBJECT_TYPE) { } else if (!isObject(value)) {
const n = normalize(value); const n = normalize(value);
if (common.typeOf(n) !== common.ARRAY_TYPE) return [n]; if (!Array.isArray(n)) return [n];
else return n; else return n;
} }
@@ -103,12 +105,14 @@ function permute(param, val): any[] {
return conditions; return conditions;
} }
export function expand(query): {} { export function expand(
query: Record<string, unknown>
): Record<string, unknown> {
const newQuery = {}; const newQuery = {};
for (const [k, v] of Object.entries(query)) { for (const [k, v] of Object.entries(query)) {
if (k[0] === "$") { if (k[0] === "$") {
// Operator // Operator
newQuery[k] = (v as any[]).map(e => expand(e)); newQuery[k] = (v as any[]).map((e) => expand(e));
} else { } else {
const conditions = permute(k, v); const conditions = permute(k, v);
if (conditions.length > 1) { if (conditions.length > 1) {
@@ -129,100 +133,16 @@ export function expand(query): {} {
return newQuery; return newQuery;
} }
function testExpressions(params, expressions, lop): boolean { export function sanitizeQueryTypes(
for (const f of expressions) { query: Record<string, unknown>,
const res = test(params, f); types: Record<string, (v: unknown) => unknown>
switch (lop) { ): Record<string, unknown> {
case "$and":
if (!res) return false;
break;
case "$or":
if (res) return true;
break;
case "$nor":
if (res) return false;
}
}
switch (lop) {
case "$and":
return true;
case "$or":
return false;
case "$nor":
return true;
default:
throw new Error("Unknown logical operator");
}
}
export function test(params, query): boolean {
let res;
for (const [k, v] of Object.entries(query)) {
if (k.charAt(0) === "$") {
// Logical operator
res = testExpressions(params, v, k);
} else {
const value = params[k];
if (common.typeOf(v) !== common.OBJECT_TYPE) {
if (common.typeOf(value) === common.ARRAY_TYPE) {
// TODO comparing array to regex, array to array, and object to object
res = value.indexOf(v) !== -1;
} else {
if (common.typeOf(v) === common.REGEXP_TYPE)
res = (v as RegExp).test(value);
else res = v === value;
}
} else {
for (const [k2, v2] of Object.entries(v)) {
switch (k2) {
case "$ne":
if (common.typeOf(value) === common.ARRAY_TYPE)
res = value.indexOf(v2) === -1;
else res = value !== v2;
break;
case "$lt":
res = value < v2;
break;
case "$lte":
res = value <= v2;
break;
case "$gt":
res = value > v2;
break;
case "$gte":
res = value >= v2;
break;
case "$regex":
res = v2.test(value);
break;
case "$in":
throw new Error("Operator not supported");
case "$nin":
throw new Error("Operator not supported");
case "$all":
throw new Error("Operator not supported");
case "$exists":
throw new Error("Operator not supported");
default:
throw new Error("Operator not supported");
}
}
}
}
if (!res) return false;
}
return true;
}
export function sanitizeQueryTypes(query, types): {} {
for (const [k, v] of Object.entries(query)) { for (const [k, v] of Object.entries(query)) {
if (k[0] === "$") { if (k[0] === "$") {
// Logical operator // Logical operator
for (const vv of v as any[]) sanitizeQueryTypes(vv, types); for (const vv of v as any[]) sanitizeQueryTypes(vv, types);
} else if (k in types) { } else if (k in types) {
if (common.typeOf(v) === common.OBJECT_TYPE) { if (isObject(v)) {
for (const [kk, vv] of Object.entries(v)) { for (const [kk, vv] of Object.entries(v)) {
switch (kk) { switch (kk) {
case "$in": case "$in":
+20 -20
View File
@@ -127,7 +127,7 @@ function random(): number {
return state.rng(); return state.rng();
} }
random.seed = function(s) { random.seed = function (s) {
state.rng = seedrandom(s); state.rng = seedrandom(s);
}; };
@@ -135,7 +135,7 @@ class ParameterWrapper {
public constructor(path: Path, attributes, unpacked?, unpackedRevision?) { public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
for (const attrName of attributes) { for (const attrName of attributes) {
Object.defineProperty(this, attrName, { Object.defineProperty(this, attrName, {
get: function() { get: function () {
if (state.uncommitted) commit(); if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
@@ -157,12 +157,12 @@ class ParameterWrapper {
if (!attr) return UNDEFINED; if (!attr) return UNDEFINED;
return attr[1]; return attr[1];
} },
}); });
} }
Object.defineProperty(this, "path", { Object.defineProperty(this, "path", {
get: function() { get: function () {
if (state.uncommitted) commit(); if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
@@ -177,11 +177,11 @@ class ParameterWrapper {
if (!unpacked.length) return UNDEFINED; if (!unpacked.length) return UNDEFINED;
return unpacked[0].toString(); return unpacked[0].toString();
} },
}); });
Object.defineProperty(this, "size", { Object.defineProperty(this, "size", {
get: function() { get: function () {
if (state.uncommitted) commit(); if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
@@ -196,10 +196,10 @@ class ParameterWrapper {
if (!unpacked.length) return UNDEFINED; if (!unpacked.length) return UNDEFINED;
return unpacked.length; return unpacked.length;
} },
}); });
this[Symbol.iterator] = function*() { this[Symbol.iterator] = function* () {
if (state.uncommitted) commit(); if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
@@ -235,7 +235,7 @@ function declare(
pathSet: null, pathSet: null,
attrGet: null, attrGet: null,
attrSet: null, attrSet: null,
defer: true defer: true,
}; };
const attrs = new Set(); const attrs = new Set();
@@ -290,9 +290,9 @@ function commit(): void {
} }
} }
function ext(): any { function ext(...args: unknown[]): any {
++state.extCounter; ++state.extCounter;
const extCall = Array.from(arguments).map(String); const extCall = args.map(String);
const key = `${state.revision}: ${JSON.stringify(extCall)}`; const key = `${state.revision}: ${JSON.stringify(extCall)}`;
if (key in state.sessionContext.extensionsCache) if (key in state.sessionContext.extensionsCache)
@@ -302,11 +302,11 @@ function ext(): any {
throw EXT; throw EXT;
} }
function log(msg: string, meta: {}): void { function log(msg: string, meta: Record<string, unknown>): void {
if (state.revision === state.maxRevision && state.extCounter >= 0) { if (state.revision === state.maxRevision && state.extCounter >= 0) {
const details = Object.assign({}, meta, { const details = Object.assign({}, meta, {
sessionContext: state.sessionContext, sessionContext: state.sessionContext,
message: `Script: ${msg}` message: `Script: ${msg}`,
}); });
delete details["hostname"]; delete details["hostname"];
@@ -342,8 +342,8 @@ function errorToFault(err: Error): Fault {
message: err.message, message: err.message,
detail: { detail: {
name: err.name, name: err.name,
message: err.message message: err.message,
} },
}; };
if (err.stack) { if (err.stack) {
@@ -365,7 +365,7 @@ function errorToFault(err: Error): Fault {
export async function run( export async function run(
script: vm.Script, script: vm.Script,
globals: {}, globals: Record<string, unknown>,
sessionContext: SessionContext, sessionContext: SessionContext,
startRevision: number, startRevision: number,
maxRevision: number, maxRevision: number,
@@ -380,7 +380,7 @@ export async function run(
extensions: {}, extensions: {},
clear: [], clear: [],
rng: null, rng: null,
extCounter: extCounter extCounter: extCounter,
}; };
for (const n of Object.keys(context)) delete context[n]; for (const n of Object.keys(context)) delete context[n];
@@ -403,7 +403,7 @@ export async function run(
clear: null, clear: null,
declare: null, declare: null,
done: false, done: false,
returnValue: null returnValue: null,
}; };
} }
} }
@@ -423,7 +423,7 @@ export async function run(
clear: null, clear: null,
declare: null, declare: null,
done: false, done: false,
returnValue: null returnValue: null,
}; };
} }
@@ -443,6 +443,6 @@ export async function run(
clear: _state.clear, clear: _state.clear,
declare: _state.declarations, declare: _state.declarations,
done: status === 0, done: status === 0,
returnValue: ret returnValue: ret,
}; };
} }
+15 -10
View File
@@ -21,10 +21,7 @@ import * as crypto from "crypto";
import * as later from "later"; import * as later from "later";
function md532(str): number { function md532(str): number {
const digest = crypto const digest = crypto.createHash("md5").update(str).digest();
.createHash("md5")
.update(str)
.digest();
return ( return (
digest.readUInt32LE(0) ^ digest.readUInt32LE(0) ^
digest.readUInt32LE(4) ^ digest.readUInt32LE(4) ^
@@ -33,29 +30,37 @@ function md532(str): number {
); );
} }
export function variance(deviceId, vrnc): number { export function variance(deviceId: string, vrnc: number): number {
return (md532(deviceId) >>> 0) % vrnc; return (md532(deviceId) >>> 0) % vrnc;
} }
export function interval(timestamp, intrvl, offset = 0): number { export function interval(
timestamp: number,
intrvl: number,
offset = 0
): number {
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset; return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
} }
export function parseCron(cronExp): any { export function parseCron(cronExp: string): any {
const parts = cronExp.trim().split(/\s+/); const parts = cronExp.trim().split(/\s+/);
if (parts.length === 5) parts.unshift("*"); if (parts.length === 5) parts.unshift("*");
return later.schedule(later.parse.cron(parts.join(" "), true)); return later.schedule(later.parse.cron(parts.join(" "), true));
} }
export function cron(timestamp, schedule, offset = 0): number[] { export function cron(
timestamp: number,
schedule: unknown,
offset = 0
): number[] {
// TODO later.js doesn't throw erorr if expression is invalid! // TODO later.js doesn't throw erorr if expression is invalid!
const ret = [0, 0]; const ret = [0, 0];
const prev = schedule.prev(1, new Date(timestamp + offset)); const prev = (schedule as any).prev(1, new Date(timestamp + offset));
if (prev) ret[0] = prev.setMilliseconds(0) - offset; if (prev) ret[0] = prev.setMilliseconds(0) - offset;
const next = schedule.next(1, new Date(timestamp + offset + 1000)); const next = (schedule as any).next(1, new Date(timestamp + offset + 1000));
if (next) ret[1] = next.setMilliseconds(0) - offset; if (next) ret[1] = next.setMilliseconds(0) - offset;
return ret; return ret;
+9 -8
View File
@@ -20,6 +20,7 @@
import * as fs from "fs"; import * as fs from "fs";
import * as http from "http"; import * as http from "http";
import * as https from "https"; import * as https from "https";
import { Socket } from "net";
import * as path from "path"; import * as path from "path";
import { ROOT_DIR } from "./config"; import { ROOT_DIR } from "./config";
@@ -52,12 +53,12 @@ function closeServer(timeout, callback): void {
} }
export function start( export function start(
port, port: number,
networkInterface, networkInterface: string,
ssl, ssl: { key: string; cert: string },
_listener, _listener: http.RequestListener,
onConnection?, onConnection?: (socket: Socket) => void,
keepAliveTimeout: number = -1 keepAliveTimeout = -1
): void { ): void {
listener = _listener; listener = _listener;
@@ -65,10 +66,10 @@ export function start(
const options = { const options = {
key: ssl.key key: ssl.key
.split(":") .split(":")
.map(f => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))), .map((f) => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))),
cert: ssl.cert cert: ssl.cert
.split(":") .split(":")
.map(f => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))) .map((f) => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))),
}; };
server = https.createServer(options, listener); server = https.createServer(options, listener);
+190 -181
View File
File diff suppressed because it is too large. Load diff
+80 -63
View File
@@ -22,7 +22,7 @@ import {
Element, Element,
parseAttrs, parseAttrs,
encodeEntities, encodeEntities,
decodeEntities decodeEntities,
} from "./xml-parser"; } from "./xml-parser";
import memoize from "./common/memoize"; import memoize from "./common/memoize";
import { version as VERSION } from "../package.json"; import { version as VERSION } from "../package.json";
@@ -36,7 +36,8 @@ import {
SpvFault, SpvFault,
CpeFault, CpeFault,
SoapMessage, SoapMessage,
TransferCompleteRequest TransferCompleteRequest,
AcsRequest,
} from "./types"; } from "./types";
const SERVER_NAME = `GenieACS/${VERSION}`; const SERVER_NAME = `GenieACS/${VERSION}`;
@@ -47,39 +48,39 @@ const NAMESPACES = {
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
xsd: "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
xsi: "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
cwmp: "urn:dslforum-org:cwmp-1-0" cwmp: "urn:dslforum-org:cwmp-1-0",
}, },
"1.1": { "1.1": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
xsd: "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
xsi: "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
cwmp: "urn:dslforum-org:cwmp-1-1" cwmp: "urn:dslforum-org:cwmp-1-1",
}, },
"1.2": { "1.2": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
xsd: "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
xsi: "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
cwmp: "urn:dslforum-org:cwmp-1-2" cwmp: "urn:dslforum-org:cwmp-1-2",
}, },
"1.3": { "1.3": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
xsd: "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
xsi: "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
cwmp: "urn:dslforum-org:cwmp-1-2" cwmp: "urn:dslforum-org:cwmp-1-2",
}, },
"1.4": { "1.4": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
xsd: "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
xsi: "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
cwmp: "urn:dslforum-org:cwmp-1-3" cwmp: "urn:dslforum-org:cwmp-1-3",
} },
}; };
let warnings; let warnings: Record<string, unknown>[];
const memoizedParseAttrs = memoize(parseAttrs); const memoizedParseAttrs = memoize(parseAttrs);
@@ -93,14 +94,16 @@ function parseBool(v): boolean {
function event(xml: Element): string[] { function event(xml: Element): string[] {
return xml.children return xml.children
.filter(n => n.localName === "EventStruct") .filter((n) => n.localName === "EventStruct")
.map(c => c.children.find(n => n.localName === "EventCode").text.trim()); .map((c) =>
c.children.find((n) => n.localName === "EventCode").text.trim()
);
} }
function parameterInfoList(xml: Element): [string, boolean][] { function parameterInfoList(xml: Element): [string, boolean][] {
return xml.children return xml.children
.filter(e => e.localName === "ParameterInfoStruct") .filter((e) => e.localName === "ParameterInfoStruct")
.map<[string, boolean]>(e => { .map<[string, boolean]>((e) => {
let param: string, value: string; let param: string, value: string;
for (const c of e.children) { for (const c of e.children) {
switch (c.localName) { switch (c.localName) {
@@ -118,7 +121,7 @@ function parameterInfoList(xml: Element): [string, boolean][] {
if (parsed === null) { if (parsed === null) {
warnings.push({ warnings.push({
message: "Invalid writable attribute", message: "Invalid writable attribute",
parameter: param parameter: param,
}); });
parsed = false; parsed = false;
} }
@@ -129,7 +132,7 @@ function parameterInfoList(xml: Element): [string, boolean][] {
const getValueType = memoize((str: string) => const getValueType = memoize((str: string) =>
parseAttrs(str) parseAttrs(str)
.find(s => s.localName === "type") .find((s) => s.localName === "type")
.value.trim() .value.trim()
); );
@@ -137,8 +140,8 @@ function parameterValueList(
xml: Element xml: Element
): [string, string | number | boolean, string][] { ): [string, string | number | boolean, string][] {
return xml.children return xml.children
.filter(e => e.localName === "ParameterValueStruct") .filter((e) => e.localName === "ParameterValueStruct")
.map<[string, string | number | boolean, string]>(e => { .map<[string, string | number | boolean, string]>((e) => {
let valueElement: Element, param: string; let valueElement: Element, param: string;
for (const c of e.children) { for (const c of e.children) {
switch (c.localName) { switch (c.localName) {
@@ -160,7 +163,7 @@ function parameterValueList(
if (parsed === null) { if (parsed === null) {
warnings.push({ warnings.push({
message: "Invalid value attribute", message: "Invalid value attribute",
parameter: param parameter: param,
}); });
parsed = value; parsed = value;
} }
@@ -169,7 +172,7 @@ function parameterValueList(
if (isNaN(parsed)) { if (isNaN(parsed)) {
warnings.push({ warnings.push({
message: "Invalid value attribute", message: "Invalid value attribute",
parameter: param parameter: param,
}); });
parsed = value; parsed = value;
} }
@@ -178,7 +181,7 @@ function parameterValueList(
if (isNaN(parsed)) { if (isNaN(parsed)) {
warnings.push({ warnings.push({
message: "Invalid value attribute", message: "Invalid value attribute",
parameter: param parameter: param,
}); });
parsed = value; parsed = value;
} }
@@ -198,8 +201,8 @@ function GetParameterNamesResponse(xml): CpeGetResponse {
return { return {
name: "GetParameterNamesResponse", name: "GetParameterNamesResponse",
parameterList: parameterInfoList( parameterList: parameterInfoList(
xml.children.find(n => n.localName === "ParameterList") xml.children.find((n) => n.localName === "ParameterList")
) ),
}; };
} }
@@ -207,7 +210,7 @@ function GetParameterValues(methodRequest): string {
return `<cwmp:GetParameterValues><ParameterNames soap-enc:arrayType="xsd:string[${ return `<cwmp:GetParameterValues><ParameterNames soap-enc:arrayType="xsd:string[${
methodRequest.parameterNames.length methodRequest.parameterNames.length
}]">${methodRequest.parameterNames }]">${methodRequest.parameterNames
.map(p => `<string>${p}</string>`) .map((p) => `<string>${p}</string>`)
.join("")}</ParameterNames></cwmp:GetParameterValues>`; .join("")}</ParameterNames></cwmp:GetParameterValues>`;
} }
@@ -215,13 +218,13 @@ function GetParameterValuesResponse(xml: Element): CpeGetResponse {
return { return {
name: "GetParameterValuesResponse", name: "GetParameterValuesResponse",
parameterList: parameterValueList( parameterList: parameterValueList(
xml.children.find(n => n.localName === "ParameterList") xml.children.find((n) => n.localName === "ParameterList")
) ),
}; };
} }
function SetParameterValues(methodRequest): string { function SetParameterValues(methodRequest): string {
const params = methodRequest.parameterList.map(p => { const params = methodRequest.parameterList.map((p) => {
let val = p[1]; let val = p[1];
if (p[2] === "xsd:dateTime" && typeof val === "number") { if (p[2] === "xsd:dateTime" && typeof val === "number") {
val = new Date(val).toISOString(); val = new Date(val).toISOString();
@@ -237,24 +240,24 @@ function SetParameterValues(methodRequest): string {
return `<cwmp:SetParameterValues><ParameterList soap-enc:arrayType="cwmp:ParameterValueStruct[${ return `<cwmp:SetParameterValues><ParameterList soap-enc:arrayType="cwmp:ParameterValueStruct[${
methodRequest.parameterList.length methodRequest.parameterList.length
}]">${params.join( }]">${params.join("")}</ParameterList><ParameterKey>${
"" methodRequest.parameterKey || ""
)}</ParameterList><ParameterKey>${methodRequest.parameterKey || }</ParameterKey></cwmp:SetParameterValues>`;
""}</ParameterKey></cwmp:SetParameterValues>`;
} }
function SetParameterValuesResponse(xml: Element): CpeSetResponse { function SetParameterValuesResponse(xml: Element): CpeSetResponse {
return { return {
name: "SetParameterValuesResponse", name: "SetParameterValuesResponse",
status: parseInt(xml.children.find(n => n.localName === "Status").text) status: parseInt(xml.children.find((n) => n.localName === "Status").text),
}; };
} }
function AddObject(methodRequest): string { function AddObject(methodRequest): string {
return `<cwmp:AddObject><ObjectName>${ return `<cwmp:AddObject><ObjectName>${
methodRequest.objectName methodRequest.objectName
}</ObjectName><ParameterKey>${methodRequest.parameterKey || }</ObjectName><ParameterKey>${
""}</ParameterKey></cwmp:AddObject>`; methodRequest.parameterKey || ""
}</ParameterKey></cwmp:AddObject>`;
} }
function AddObjectResponse(xml: Element): CpeSetResponse { function AddObjectResponse(xml: Element): CpeSetResponse {
@@ -273,32 +276,34 @@ function AddObjectResponse(xml: Element): CpeSetResponse {
return { return {
name: "AddObjectResponse", name: "AddObjectResponse",
instanceNumber: instanceNumber, instanceNumber: instanceNumber,
status: status status: status,
}; };
} }
function DeleteObject(methodRequest): string { function DeleteObject(methodRequest): string {
return `<cwmp:DeleteObject><ObjectName>${ return `<cwmp:DeleteObject><ObjectName>${
methodRequest.objectName methodRequest.objectName
}</ObjectName><ParameterKey>${methodRequest.parameterKey || }</ObjectName><ParameterKey>${
""}</ParameterKey></cwmp:DeleteObject>`; methodRequest.parameterKey || ""
}</ParameterKey></cwmp:DeleteObject>`;
} }
function DeleteObjectResponse(xml: Element): CpeSetResponse { function DeleteObjectResponse(xml: Element): CpeSetResponse {
return { return {
name: "DeleteObjectResponse", name: "DeleteObjectResponse",
status: parseInt(xml.children.find(n => n.localName === "Status").text) status: parseInt(xml.children.find((n) => n.localName === "Status").text),
}; };
} }
function Reboot(methodRequest): string { function Reboot(methodRequest): string {
return `<cwmp:Reboot><CommandKey>${methodRequest.commandKey || return `<cwmp:Reboot><CommandKey>${
""}</CommandKey></cwmp:Reboot>`; methodRequest.commandKey || ""
}</CommandKey></cwmp:Reboot>`;
} }
function RebootResponse(): CpeSetResponse { function RebootResponse(): CpeSetResponse {
return { return {
name: "RebootResponse" name: "RebootResponse",
}; };
} }
@@ -308,23 +313,26 @@ function FactoryReset(): string {
function FactoryResetResponse(): CpeSetResponse { function FactoryResetResponse(): CpeSetResponse {
return { return {
name: "FactoryResetResponse" name: "FactoryResetResponse",
}; };
} }
function Download(methodRequest): string { function Download(methodRequest): string {
return `<cwmp:Download><CommandKey>${methodRequest.commandKey || return `<cwmp:Download><CommandKey>${
""}</CommandKey><FileType>${methodRequest.fileType}</FileType><URL>${ methodRequest.commandKey || ""
}</CommandKey><FileType>${methodRequest.fileType}</FileType><URL>${
methodRequest.url methodRequest.url
}</URL><Username>${encodeEntities( }</URL><Username>${encodeEntities(
methodRequest.username || "" methodRequest.username || ""
)}</Username><Password>${encodeEntities( )}</Username><Password>${encodeEntities(
methodRequest.password || "" methodRequest.password || ""
)}</Password><FileSize>${methodRequest.fileSize || )}</Password><FileSize>${
"0"}</FileSize><TargetFileName>${encodeEntities( methodRequest.fileSize || "0"
}</FileSize><TargetFileName>${encodeEntities(
methodRequest.targetFileName || "" methodRequest.targetFileName || ""
)}</TargetFileName><DelaySeconds>${methodRequest.delaySeconds || )}</TargetFileName><DelaySeconds>${
"0"}</DelaySeconds><SuccessURL>${encodeEntities( methodRequest.delaySeconds || "0"
}</DelaySeconds><SuccessURL>${encodeEntities(
methodRequest.successUrl || "" methodRequest.successUrl || ""
)}</SuccessURL><FailureURL>${encodeEntities( )}</SuccessURL><FailureURL>${encodeEntities(
methodRequest.failureUrl || "" methodRequest.failureUrl || ""
@@ -351,7 +359,7 @@ function DownloadResponse(xml: Element): CpeSetResponse {
name: "DownloadResponse", name: "DownloadResponse",
status: status, status: status,
startTime: startTime, startTime: startTime,
completeTime: completeTime completeTime: completeTime,
}; };
} }
@@ -361,7 +369,7 @@ function Inform(xml: Element): InformRequest {
Manufacturer: null, Manufacturer: null,
OUI: null, OUI: null,
ProductClass: null, ProductClass: null,
SerialNumber: null SerialNumber: null,
}; };
for (const c of xml.children) { for (const c of xml.children) {
@@ -389,7 +397,7 @@ function Inform(xml: Element): InformRequest {
parameterList: parameterList, parameterList: parameterList,
deviceId: deviceId, deviceId: deviceId,
event: evnt, event: evnt,
retryCount: retryCount retryCount: retryCount,
}; };
} }
@@ -405,7 +413,7 @@ function GetRPCMethodsResponse(methodResponse): string {
return `<cwmp:GetRPCMethodsResponse><MethodList soap-enc:arrayType="xsd:string[${ return `<cwmp:GetRPCMethodsResponse><MethodList soap-enc:arrayType="xsd:string[${
methodResponse.methodList.length methodResponse.methodList.length
}]">${methodResponse.methodList }]">${methodResponse.methodList
.map(m => `<string>${m}</string>`) .map((m) => `<string>${m}</string>`)
.join("")}</MethodList></cwmp:GetRPCMethodsResponse>`; .join("")}</MethodList></cwmp:GetRPCMethodsResponse>`;
} }
@@ -433,7 +441,7 @@ function TransferComplete(xml: Element): TransferCompleteRequest {
commandKey: commandKey, commandKey: commandKey,
faultStruct: _faultStruct, faultStruct: _faultStruct,
startTime: startTime, startTime: startTime,
completeTime: completeTime completeTime: completeTime,
}; };
} }
@@ -444,7 +452,7 @@ function TransferCompleteResponse(): string {
function RequestDownload(xml: Element): CpeRequest { function RequestDownload(xml: Element): CpeRequest {
return { return {
name: "RequestDownload", name: "RequestDownload",
fileType: xml.children.find(n => n.localName === "FileType").text fileType: xml.children.find((n) => n.localName === "FileType").text,
}; };
} }
@@ -480,7 +488,7 @@ function faultStruct(xml: Element): FaultStruct {
setParameterValuesFault.push({ setParameterValuesFault.push({
parameterName: pn, parameterName: pn,
faultCode: fc, faultCode: fc,
faultString: fs faultString: fs,
}); });
} }
} }
@@ -499,7 +507,7 @@ function fault(xml: Element): CpeFault {
faultString = decodeEntities(c.text); faultString = decodeEntities(c.text);
break; break;
case "detail": case "detail":
detail = faultStruct(c.children.find(n => n.localName === "Fault")); detail = faultStruct(c.children.find((n) => n.localName === "Fault"));
break; break;
} }
} }
@@ -507,7 +515,11 @@ function fault(xml: Element): CpeFault {
return { faultCode, faultString, detail }; return { faultCode, faultString, detail };
} }
export function request(body: string, cwmpVersion, warn): SoapMessage { export function request(
body: string,
cwmpVersion: string,
warn: Record<string, unknown>[]
): SoapMessage {
warnings = warn; warnings = warn;
const rpc = { const rpc = {
@@ -516,7 +528,7 @@ export function request(body: string, cwmpVersion, warn): SoapMessage {
sessionTimeout: null, sessionTimeout: null,
cpeRequest: null, cpeRequest: null,
cpeFault: null, cpeFault: null,
cpeResponse: null cpeResponse: null,
}; };
if (!body.length) return rpc; if (!body.length) return rpc;
@@ -563,9 +575,9 @@ export function request(body: string, cwmpVersion, warn): SoapMessage {
const attrs = memoizedParseAttrs(e.attrs); const attrs = memoizedParseAttrs(e.attrs);
const attr = namespace const attr = namespace
? attrs.find( ? attrs.find(
s => s.namespace === "xmlns" && s.localName === namespace (s) => s.namespace === "xmlns" && s.localName === namespace
) )
: attrs.find(s => s.name === "xmlns"); : attrs.find((s) => s.name === "xmlns");
if (attr) namespaceHref = attr.value; if (attr) namespaceHref = attr.value;
} }
@@ -653,13 +665,18 @@ const namespacesAttrs = {
.join(" "), .join(" "),
"1.4": Object.entries(NAMESPACES["1.4"]) "1.4": Object.entries(NAMESPACES["1.4"])
.map(([k, v]) => `xmlns:${k}="${v}"`) .map(([k, v]) => `xmlns:${k}="${v}"`)
.join(" ") .join(" "),
}; };
export function response(rpc): { code: number; headers: {}; data: string } { export function response(rpc: {
id: string;
acsRequest?: AcsRequest;
acsResponse?: AcsResponse;
cwmpVersion?: string;
}): { code: number; headers: Record<string, string>; data: string } {
const headers = { const headers = {
Server: SERVER_NAME, Server: SERVER_NAME,
SOAPServer: SERVER_NAME SOAPServer: SERVER_NAME,
}; };
if (!rpc) return { code: 204, headers: headers, data: "" }; if (!rpc) return { code: 204, headers: headers, data: "" };
@@ -721,6 +738,6 @@ export function response(rpc): { code: number; headers: {}; data: string } {
namespacesAttrs[rpc.cwmpVersion] namespacesAttrs[rpc.cwmpVersion]
}><soap-env:Header><cwmp:ID soap-env:mustUnderstand="1">${ }><soap-env:Header><cwmp:ID soap-env:mustUnderstand="1">${
rpc.id rpc.id
}</cwmp:ID></soap-env:Header><soap-env:Body>${body}</soap-env:Body></soap-env:Envelope>` }</cwmp:ID></soap-env:Header><soap-env:Body>${body}</soap-env:Body></soap-env:Envelope>`,
}; };
} }
+8 -8
View File
@@ -152,7 +152,7 @@ export interface SessionContext {
} }
export interface Task { export interface Task {
_id: string; _id?: string;
name: string; name: string;
parameterNames?: string[]; parameterNames?: string[];
parameterValues?: [string, string | number | boolean, string?][]; parameterValues?: [string, string | number | boolean, string?][];
@@ -300,7 +300,7 @@ export interface QueryOptions {
export interface Declaration { export interface Declaration {
path: Path; path: Path;
pathGet: number; pathGet: number;
pathSet?: [number, number]; pathSet?: number | [number, number];
attrGet?: { object?: number; writable?: number; value?: number }; attrGet?: { object?: number; writable?: number; value?: number };
attrSet?: { attrSet?: {
object?: boolean; object?: boolean;
@@ -322,7 +322,7 @@ export interface Preset {
channel: string; channel: string;
schedule?: { md5: string; duration: number; schedule: any }; schedule?: { md5: string; duration: number; schedule: any };
events?: { [event: string]: boolean }; events?: { [event: string]: boolean };
precondition?: {}; precondition?: Expression;
provisions: string[][]; provisions: string[][];
} }
@@ -367,12 +367,12 @@ export interface Config {
} }
export interface UiConfig { export interface UiConfig {
filters: {}; filters: Record<string, unknown>;
device: {}; device: Record<string, unknown>;
index: {}; index: Record<string, unknown>;
overview: { overview: {
charts?: {}; charts?: Record<string, unknown>;
groups?: {}; groups?: Record<string, unknown>;
}; };
pageSize?: Expression; pageSize?: Expression;
} }
+13 -13
View File
@@ -52,14 +52,14 @@ const getAuthorizer = memoize(
(snapshot: string, rolesStr: string): Authorizer => { (snapshot: string, rolesStr: string): Authorizer => {
const roles: string[] = JSON.parse(rolesStr); const roles: string[] = JSON.parse(rolesStr);
const allPermissions = localCache.getPermissions(snapshot); const allPermissions = localCache.getPermissions(snapshot);
const permissionSets: PermissionSet[] = roles.map(r => const permissionSets: PermissionSet[] = roles.map((r) =>
Object.values(allPermissions[r] || {}) Object.values(allPermissions[r] || {})
); );
return new Authorizer(permissionSets); return new Authorizer(permissionSets);
} }
); );
koa.on("error", async err => { koa.on("error", async (err) => {
throw err; throw err;
}); });
@@ -84,7 +84,7 @@ koa.use(
} }
return true; return true;
} },
}) })
); );
@@ -111,7 +111,7 @@ koa.use(async (ctx, next) => {
return next(); return next();
}); });
router.post("/login", async ctx => { router.post("/login", async (ctx) => {
if (!JWT_SECRET) { if (!JWT_SECRET) {
ctx.status = 500; ctx.status = 500;
ctx.body = "UI_JWT_SECRET is not set"; ctx.body = "UI_JWT_SECRET is not set";
@@ -126,7 +126,7 @@ router.post("/login", async ctx => {
message: "Log in", message: "Log in",
context: ctx, context: ctx,
username: username, username: username,
method: null method: null,
}; };
function success(authMethod): void { function success(authMethod): void {
@@ -150,13 +150,13 @@ router.post("/login", async ctx => {
failure(); failure();
}); });
router.post("/logout", async ctx => { router.post("/logout", async (ctx) => {
ctx.cookies.set(JWT_COOKIE); // Delete cookie ctx.cookies.set(JWT_COOKIE); // Delete cookie
ctx.body = ""; ctx.body = "";
logger.accessInfo({ logger.accessInfo({
message: "Log out", message: "Log out",
context: ctx context: ctx,
}); });
}); });
@@ -170,11 +170,11 @@ koa.use(async (ctx, next) => {
koa.use(koaBodyParser()); koa.use(koaBodyParser());
router.use("/api", api.routes(), api.allowedMethods()); router.use("/api", api.routes(), api.allowedMethods());
router.get("/status", ctx => { router.get("/status", (ctx) => {
ctx.body = "OK"; ctx.body = "OK";
}); });
router.get("/init", async ctx => { router.get("/init", async (ctx) => {
const status = await init.getStatus(); const status = await init.getStatus();
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) { if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false; if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
@@ -195,7 +195,7 @@ router.get("/init", async ctx => {
ctx.body = status; ctx.body = status;
}); });
router.post("/init", async ctx => { router.post("/init", async (ctx) => {
const status = ctx.request.body; const status = ctx.request.body;
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) { if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false; if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
@@ -216,7 +216,7 @@ router.post("/init", async ctx => {
ctx.body = ""; ctx.body = "";
}); });
router.get("/", async ctx => { router.get("/", async (ctx) => {
const permissionSets: PermissionSet[] = ctx.state.authorizer.getPermissionSets(); const permissionSets: PermissionSet[] = ctx.state.authorizer.getPermissionSets();
let wizard = ""; let wizard = "";
@@ -234,7 +234,7 @@ router.get("/", async ctx => {
<noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript> <noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript>
<script> <script>
window.clientConfig = ${JSON.stringify({ window.clientConfig = ${JSON.stringify({
ui: localCache.getUiConfig(ctx.state.configSnapshot) ui: localCache.getUiConfig(ctx.state.configSnapshot),
})}; })};
window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)}; window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)};
window.genieacsVersion = ${JSON.stringify(VERSION)}; window.genieacsVersion = ${JSON.stringify(VERSION)};
@@ -251,7 +251,7 @@ router.get("/", async ctx => {
koa.use( koa.use(
koaCompress({ koaCompress({
flush: Z_SYNC_FLUSH flush: Z_SYNC_FLUSH,
}) })
); );
+30 -17
View File
@@ -26,8 +26,9 @@ import {
insertTasks, insertTasks,
watchTask, watchTask,
connectionRequest, connectionRequest,
deleteDevice deleteDevice,
} from "../api-functions"; } from "../api-functions";
import { Task } from "../types";
async function deleteFault(id): Promise<void> { async function deleteFault(id): Promise<void> {
const deviceId = id.split(":", 1)[0]; const deviceId = id.split(":", 1)[0];
@@ -37,13 +38,16 @@ async function deleteFault(id): Promise<void> {
db.deleteFault(id), db.deleteFault(id),
channel.startsWith("task_") channel.startsWith("task_")
? db.deleteTask(new ObjectID(channel.slice(5))) ? db.deleteTask(new ObjectID(channel.slice(5)))
: null : null,
]); ]);
await del(`${deviceId}_tasks_faults_operations`); await del(`${deviceId}_tasks_faults_operations`);
} }
export async function deleteResource(resource, id): Promise<void> { export async function deleteResource(
resource: string,
id: string
): Promise<void> {
switch (resource) { switch (resource) {
case "devices": case "devices":
await deleteDevice(id); await deleteDevice(id);
@@ -86,18 +90,18 @@ export async function deleteResource(resource, id): Promise<void> {
} }
export async function postTasks( export async function postTasks(
deviceId, deviceId: string,
tasks, tasks: Task[],
timeout, timeout: number,
device device: Record<string, { value?: [boolean | number | string, string] }>
): Promise<{ connectionRequest: string; tasks: any[] }> { ): Promise<{ connectionRequest: string; tasks: any[] }> {
for (const task of tasks) { for (const task of tasks) {
delete task._id; delete task._id;
task.device = deviceId; task["device"] = deviceId;
} }
tasks = await insertTasks(tasks); tasks = await insertTasks(tasks);
const statuses = tasks.map(t => { const statuses = tasks.map((t) => {
return { _id: t._id, status: "pending" }; return { _id: t._id, status: "pending" };
}); });
@@ -106,9 +110,10 @@ export async function postTasks(
try { try {
await connectionRequest(deviceId, device); await connectionRequest(deviceId, device);
} catch (err) { } catch (err) {
for (const t of statuses) db.deleteTask(new ObjectID(t._id));
return { return {
connectionRequest: err.message, connectionRequest: err.message,
tasks: statuses tasks: statuses,
}; };
} }
@@ -131,9 +136,9 @@ export async function postTasks(
r.status = "done"; r.status = "done";
} else if (res[i * 2 + 1].length === 1) { } else if (res[i * 2 + 1].length === 1) {
r.status = "fault"; r.status = "fault";
r.fault = res[i * 2 + 1][0]; r["fault"] = res[i * 2 + 1][0];
} }
db.deleteTask(r._id); db.deleteTask(new ObjectID(r._id));
} }
return { connectionRequest: "OK", tasks: statuses }; return { connectionRequest: "OK", tasks: statuses };
@@ -149,7 +154,11 @@ interface PingResponse {
mdev: number; mdev: number;
} }
export async function putResource(resource, id, data): Promise<void> { export async function putResource(
resource: string,
id: string,
data: Record<string, unknown>
): Promise<void> {
if (resource === "presets") { if (resource === "presets") {
await db.putPreset(id, data); await db.putPreset(id, data);
} else if (resource === "provisions") { } else if (resource === "provisions") {
@@ -161,21 +170,25 @@ export async function putResource(resource, id, data): Promise<void> {
} else if (resource === "permissions") { } else if (resource === "permissions") {
await db.putPermission(id, data); await db.putPermission(id, data);
} else if (resource === "users") { } else if (resource === "users") {
delete data.password; delete data["password"];
delete data.salt; delete data["salt"];
await db.putUser(id, data); await db.putUser(id, data);
} }
await del("presets_hash"); await del("presets_hash");
} }
export function authLocal(snapshot, username, password): Promise<boolean> { export function authLocal(
snapshot: string,
username: string,
password: string
): Promise<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const users = getUsers(snapshot); const users = getUsers(snapshot);
const user = users[username]; const user = users[username];
if (!user || !user.password) return void resolve(null); if (!user || !user.password) return void resolve(null);
hashPassword(password, user.salt) hashPassword(password, user.salt)
.then(hash => { .then((hash) => {
if (hash === user.password) resolve(true); if (hash === user.password) resolve(true);
else resolve(false); else resolve(false);
}) })
+41 -39
View File
@@ -53,7 +53,7 @@ const RESOURCE_IDS = {
permissions: "_id", permissions: "_id",
users: "_id", users: "_id",
faults: "_id", faults: "_id",
tasks: "_id" tasks: "_id",
}; };
const resources = { const resources = {
@@ -66,21 +66,21 @@ const resources = {
permissions: 0 | RESOURCE_DELETE | RESOURCE_PUT, permissions: 0 | RESOURCE_DELETE | RESOURCE_PUT,
users: 0 | RESOURCE_DELETE | RESOURCE_PUT, users: 0 | RESOURCE_DELETE | RESOURCE_PUT,
faults: 0 | RESOURCE_DELETE, faults: 0 | RESOURCE_DELETE,
tasks: 0 tasks: 0,
}; };
router.get(`/devices/:id.csv`, async ctx => { router.get(`/devices/:id.csv`, async (ctx) => {
const authorizer: Authorizer = ctx.state.authorizer; const authorizer: Authorizer = ctx.state.authorizer;
const log = { const log = {
message: "Query device (CSV)", message: "Query device (CSV)",
context: ctx, context: ctx,
id: ctx.params.id id: ctx.params.id,
}; };
const filter = and(authorizer.getFilter("devices", 2), [ const filter = and(authorizer.getFilter("devices", 2), [
"=", "=",
["PARAM", RESOURCE_IDS.devices], ["PARAM", RESOURCE_IDS.devices],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess("devices", 2)) { if (!authorizer.hasAccess("devices", 2)) {
@@ -113,9 +113,9 @@ router.get(`/devices/:id.csv`, async ctx => {
p.writableTimestamp, p.writableTimestamp,
p.value != null ? `"${p.value[0].toString().replace(/"/g, '""')}"` : "", p.value != null ? `"${p.value[0].toString().replace(/"/g, '""')}"` : "",
p.value != null ? p.value[1] : "", p.value != null ? p.value[1] : "",
p.valueTimestamp p.valueTimestamp,
]; ];
ctx.body.write(row.map(r => (r != null ? r : "")).join(",") + "\n"); ctx.body.write(row.map((r) => (r != null ? r : "")).join(",") + "\n");
} }
ctx.body.end(); ctx.body.end();
logger.accessInfo(log); logger.accessInfo(log);
@@ -132,7 +132,7 @@ for (const [resource, flags] of Object.entries(resources)) {
message: `Count ${resource}`, message: `Count ${resource}`,
context: ctx, context: ctx,
filter: ctx.request.query.filter, filter: ctx.request.query.filter,
count: null count: null,
}; };
if (!authorizer.hasAccess(resource, 1)) { if (!authorizer.hasAccess(resource, 1)) {
@@ -144,7 +144,7 @@ for (const [resource, flags] of Object.entries(resources)) {
if (resource === "tasks" || resource === "faults") { if (resource === "tasks" || resource === "faults") {
filter = and(filter, [ filter = and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000],
]); ]);
} }
@@ -180,7 +180,7 @@ for (const [resource, flags] of Object.entries(resources)) {
limit: options.limit, limit: options.limit,
skip: options.skip, skip: options.skip,
sort: options.sort, sort: options.sort,
projection: options.projection projection: options.projection,
}; };
if (!authorizer.hasAccess(resource, 2)) { if (!authorizer.hasAccess(resource, 2)) {
@@ -192,7 +192,7 @@ for (const [resource, flags] of Object.entries(resources)) {
if (resource === "tasks" || resource === "faults") { if (resource === "tasks" || resource === "faults") {
filter = and(filter, [ filter = and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000],
]); ]);
} }
@@ -201,7 +201,7 @@ for (const [resource, flags] of Object.entries(resources)) {
let c = 0; let c = 0;
ctx.body.write("[\n"); ctx.body.write("[\n");
await db.query(resource, filter, options, obj => { await db.query(resource, filter, options, (obj) => {
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n"); ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
}); });
ctx.body.end("]"); ctx.body.end("]");
@@ -227,7 +227,7 @@ for (const [resource, flags] of Object.entries(resources)) {
filter: ctx.request.query.filter, filter: ctx.request.query.filter,
limit: options.limit, limit: options.limit,
skip: options.skip, skip: options.skip,
sort: options.sort sort: options.sort,
}; };
if (!authorizer.hasAccess(resource, 2)) { if (!authorizer.hasAccess(resource, 2)) {
@@ -235,7 +235,9 @@ for (const [resource, flags] of Object.entries(resources)) {
return void next(); return void next();
} }
const columns = JSON.parse(ctx.request.query.columns); const columns: Record<string, Expression> = JSON.parse(
ctx.request.query.columns
);
const now = Date.now(); const now = Date.now();
for (const [k, v] of Object.entries(columns)) { for (const [k, v] of Object.entries(columns)) {
@@ -249,7 +251,7 @@ for (const [resource, flags] of Object.entries(resources)) {
if (resource === "tasks" || resource === "faults") { if (resource === "tasks" || resource === "faults") {
filter = and(filter, [ filter = and(filter, [
"NOT", "NOT",
["<", ["PARAM", "expiry"], Date.now() + 60000] ["<", ["PARAM", "expiry"], Date.now() + 60000],
]); ]);
} }
@@ -260,11 +262,11 @@ for (const [resource, flags] of Object.entries(resources)) {
); );
ctx.body.write( ctx.body.write(
Object.keys(columns).map(k => `"${k.replace(/"/, '""')}"`) + "\n" Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n"
); );
await db.query(resource, filter, options, obj => { await db.query(resource, filter, options, (obj) => {
const arr = Object.values(columns).map(exp => { const arr = Object.values(columns).map((exp) => {
const v = evaluate(exp, obj, null, e => { const v = evaluate(exp, obj, null, (e) => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
if (e[0] === "PARAM") { if (e[0] === "PARAM") {
if (resource === "devices") { if (resource === "devices") {
@@ -303,13 +305,13 @@ for (const [resource, flags] of Object.entries(resources)) {
const log = { const log = {
message: `Count ${resource}`, message: `Count ${resource}`,
context: ctx, context: ctx,
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"` filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`,
}; };
const filter = and(authorizer.getFilter(resource, 2), [ const filter = and(authorizer.getFilter(resource, 2), [
"=", "=",
["PARAM", RESOURCE_IDS[resource]], ["PARAM", RESOURCE_IDS[resource]],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess(resource, 2)) { if (!authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
@@ -329,13 +331,13 @@ for (const [resource, flags] of Object.entries(resources)) {
const log = { const log = {
message: `Query ${resource}`, message: `Query ${resource}`,
context: ctx, context: ctx,
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"` filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`,
}; };
const filter = and(authorizer.getFilter(resource, 2), [ const filter = and(authorizer.getFilter(resource, 2), [
"=", "=",
["PARAM", RESOURCE_IDS[resource]], ["PARAM", RESOURCE_IDS[resource]],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess(resource, 2)) { if (!authorizer.hasAccess(resource, 2)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
@@ -356,13 +358,13 @@ for (const [resource, flags] of Object.entries(resources)) {
const log = { const log = {
message: `Delete ${resource}`, message: `Delete ${resource}`,
context: ctx, context: ctx,
id: ctx.params.id id: ctx.params.id,
}; };
const filter = and(authorizer.getFilter(resource, 3), [ const filter = and(authorizer.getFilter(resource, 3), [
"=", "=",
["PARAM", RESOURCE_IDS[resource]], ["PARAM", RESOURCE_IDS[resource]],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess(resource, 3)) { if (!authorizer.hasAccess(resource, 3)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
@@ -393,7 +395,7 @@ for (const [resource, flags] of Object.entries(resources)) {
const log = { const log = {
message: `Put ${resource}`, message: `Put ${resource}`,
context: ctx, context: ctx,
id: id id: id,
}; };
if (!authorizer.hasAccess(resource, 3)) { if (!authorizer.hasAccess(resource, 3)) {
@@ -434,7 +436,7 @@ router.put("/files/:id", async (ctx, next) => {
message: `Upload ${resource}`, message: `Upload ${resource}`,
context: ctx, context: ctx,
id: id, id: id,
metadata: null metadata: null,
}; };
if (!authorizer.hasAccess(resource, 3)) { if (!authorizer.hasAccess(resource, 3)) {
@@ -446,7 +448,7 @@ router.put("/files/:id", async (ctx, next) => {
fileType: ctx.request.headers["metadata.filetype"] || "", fileType: ctx.request.headers["metadata.filetype"] || "",
oui: ctx.request.headers["metadata.oui"] || "", oui: ctx.request.headers["metadata.oui"] || "",
productClass: ctx.request.headers["metadata.productclass"] || "", productClass: ctx.request.headers["metadata.productclass"] || "",
version: ctx.request.headers["metadata.version"] || "" version: ctx.request.headers["metadata.version"] || "",
}; };
const validate = authorizer.getValidator(resource, metadata); const validate = authorizer.getValidator(resource, metadata);
@@ -474,13 +476,13 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
message: "Commit tasks", message: "Commit tasks",
context: ctx, context: ctx,
deviceId: ctx.params.id, deviceId: ctx.params.id,
tasks: null tasks: null,
}; };
const filter = and(authorizer.getFilter("devices", 3), [ const filter = and(authorizer.getFilter("devices", 3), [
"=", "=",
["PARAM", "DeviceID.ID"], ["PARAM", "DeviceID.ID"],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess("devices", 3)) { if (!authorizer.hasAccess("devices", 3)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
@@ -503,7 +505,7 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
"cwmp.deviceOnlineThreshold", "cwmp.deviceOnlineThreshold",
{}, {},
Date.now(), Date.now(),
exp => { (exp) => {
if (!Array.isArray(exp)) return exp; if (!Array.isArray(exp)) return exp;
if (exp[0] === "PARAM") { if (exp[0] === "PARAM") {
const p = device[exp[1]]; const p = device[exp[1]];
@@ -519,7 +521,7 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
} }
return exp; return exp;
} }
); ) as number;
const res = await apiFunctions.postTasks( const res = await apiFunctions.postTasks(
ctx.params.id, ctx.params.id,
@@ -528,7 +530,7 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
device device
); );
log.tasks = res.tasks.map(t => t._id).join(","); log.tasks = res.tasks.map((t) => t._id).join(",");
logger.accessInfo(log); logger.accessInfo(log);
@@ -542,13 +544,13 @@ router.post("/devices/:id/tags", async (ctx, next) => {
message: "Update tags", message: "Update tags",
context: ctx, context: ctx,
deviceId: ctx.params.id, deviceId: ctx.params.id,
tags: ctx.request.body tags: ctx.request.body,
}; };
const filter = and(authorizer.getFilter("devices", 3), [ const filter = and(authorizer.getFilter("devices", 3), [
"=", "=",
["PARAM", "DeviceID.ID"], ["PARAM", "DeviceID.ID"],
ctx.params.id ctx.params.id,
]); ]);
if (!authorizer.hasAccess("devices", 3)) { if (!authorizer.hasAccess("devices", 3)) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
@@ -577,8 +579,8 @@ router.post("/devices/:id/tags", async (ctx, next) => {
ctx.body = ""; ctx.body = "";
}); });
router.get("/ping/:host", async ctx => { router.get("/ping/:host", async (ctx) => {
return new Promise(resolve => { return new Promise((resolve) => {
ping(ctx.params.host, (err, parsed) => { ping(ctx.params.host, (err, parsed) => {
if (parsed) { if (parsed) {
ctx.body = parsed; ctx.body = parsed;
@@ -597,7 +599,7 @@ router.put("/users/:id/password", async (ctx, next) => {
const log = { const log = {
message: "Change password", message: "Change password",
context: ctx, context: ctx,
username: username username: username,
}; };
if (!ctx.state.user) { if (!ctx.state.user) {
@@ -622,7 +624,7 @@ router.put("/users/:id/password", async (ctx, next) => {
const filter = and(authorizer.getFilter("users", 3), [ const filter = and(authorizer.getFilter("users", 3), [
"=", "=",
["PARAM", RESOURCE_IDS.users], ["PARAM", RESOURCE_IDS.users],
username username,
]); ]);
const res = await db.query("users", filter); const res = await db.query("users", filter);
if (!res.length) return void next(); if (!res.length) return void next();
+83 -50
View File
@@ -17,19 +17,20 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { MongoClient, GridFSBucket } from "mongodb"; import { MongoClient, GridFSBucket, ObjectID } from "mongodb";
import { Script } from "vm"; import { Script } from "vm";
import * as config from "../config"; import * as config from "../config";
import * as mongodbFunctions from "../mongodb-functions"; import * as mongodbFunctions from "../mongodb-functions";
import * as expression from "../common/expression"; import * as expression from "../common/expression";
import { QueryOptions } from "../types"; import { QueryOptions, Expression } from "../types";
import { Readable } from "stream";
const CACHE_TTL = 300000; const CACHE_TTL = 300000;
let clientPromise: Promise<MongoClient>; let clientPromise: Promise<MongoClient>;
const RESOURCE_COLLECTION = { const RESOURCE_COLLECTION = {
files: "fs.files" files: "fs.files",
}; };
function ensureIndexes(client): void { function ensureIndexes(client): void {
@@ -58,27 +59,31 @@ function getClient(): Promise<MongoClient> {
return clientPromise; return clientPromise;
} }
export function cache<T>(key, valueGetter: () => Promise<T>, ttl): Promise<T> { export function cache<T>(
key: string,
valueGetter: () => Promise<T>,
ttl: number
): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const collection = client.db().collection("cache"); const collection = client.db().collection("cache");
collection.findOne({ _id: key }, (err, doc) => { collection.findOne({ _id: key }, (err, doc) => {
if (err) return void reject(err); if (err) return void reject(err);
if (doc != null) return void resolve(JSON.parse(doc.value) as T); if (doc != null) return void resolve(JSON.parse(doc.value) as T);
valueGetter() valueGetter()
.then(res => { .then((res) => {
const expire = Date.now() + (ttl || CACHE_TTL); const expire = Date.now() + (ttl || CACHE_TTL);
const cacheDoc = { const cacheDoc = {
_id: key, _id: key,
value: JSON.stringify(res), value: JSON.stringify(res),
expire: new Date(expire) expire: new Date(expire),
}; };
collection.updateOne( collection.updateOne(
{ _id: key }, { _id: key },
{ $set: cacheDoc }, { $set: cacheDoc },
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) reject(err); if (err) reject(err);
else resolve(res); else resolve(res);
} }
@@ -92,8 +97,8 @@ export function cache<T>(key, valueGetter: () => Promise<T>, ttl): Promise<T> {
} }
export function query( export function query(
resource, resource: string,
filter, filter: Expression,
options?: QueryOptions, options?: QueryOptions,
callback?: (doc: any) => void callback?: (doc: any) => void
): Promise<any[]> { ): Promise<any[]> {
@@ -121,7 +126,7 @@ export function query(
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient().then(client => { getClient().then((client) => {
const collection = client const collection = client
.db() .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
@@ -160,20 +165,20 @@ export function query(
cursor.toArray((err, docs) => { cursor.toArray((err, docs) => {
if (err) return reject(err); if (err) return reject(err);
if (resource === "devices") if (resource === "devices")
docs = docs.map(d => mongodbFunctions.flattenDevice(d)); docs = docs.map((d) => mongodbFunctions.flattenDevice(d));
else if (resource === "faults") else if (resource === "faults")
docs = docs.map(d => mongodbFunctions.flattenFault(d)); docs = docs.map((d) => mongodbFunctions.flattenFault(d));
else if (resource === "tasks") else if (resource === "tasks")
docs = docs.map(d => mongodbFunctions.flattenTask(d)); docs = docs.map((d) => mongodbFunctions.flattenTask(d));
else if (resource === "presets") else if (resource === "presets")
docs = docs.map(d => mongodbFunctions.flattenPreset(d)); docs = docs.map((d) => mongodbFunctions.flattenPreset(d));
else if (resource === "files") else if (resource === "files")
docs = docs.map(d => mongodbFunctions.flattenFile(d)); docs = docs.map((d) => mongodbFunctions.flattenFile(d));
return resolve(docs); return resolve(docs);
}); });
} else { } else {
cursor.forEach( cursor.forEach(
doc => { (doc) => {
if (resource === "devices") if (resource === "devices")
doc = mongodbFunctions.flattenDevice(doc); doc = mongodbFunctions.flattenDevice(doc);
else if (resource === "faults") else if (resource === "faults")
@@ -186,7 +191,7 @@ export function query(
doc = mongodbFunctions.flattenFile(doc); doc = mongodbFunctions.flattenFile(doc);
callback(doc); callback(doc);
}, },
err => { (err) => {
if (err) reject(err); if (err) reject(err);
else resolve(); else resolve();
} }
@@ -196,7 +201,7 @@ export function query(
}); });
} }
export function count(resource, filter): Promise<number> { export function count(resource: string, filter: Expression): Promise<number> {
let q; let q;
filter = expression.evaluate(filter, null, Date.now()); filter = expression.evaluate(filter, null, Date.now());
@@ -213,7 +218,7 @@ export function count(resource, filter): Promise<number> {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient().then(client => { getClient().then((client) => {
const collection = client const collection = client
.db() .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
@@ -225,7 +230,10 @@ export function count(resource, filter): Promise<number> {
}); });
} }
export function updateDeviceTags(deviceId, tags): Promise<void> { export function updateDeviceTags(
deviceId: string,
tags: Record<string, boolean>
): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const add = []; const add = [];
const pull = []; const pull = [];
@@ -242,13 +250,13 @@ export function updateDeviceTags(deviceId, tags): Promise<void> {
} }
} }
getClient() getClient()
.then(client => { .then((client) => {
const collection = client.db().collection("devices"); const collection = client.db().collection("devices");
const object = {}; const object = {};
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } }; if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
if (pull && pull.length) object["$pullAll"] = { _tags: pull }; if (pull && pull.length) object["$pullAll"] = { _tags: pull };
collection.updateOne({ _id: deviceId }, object, err => { collection.updateOne({ _id: deviceId }, object, (err) => {
if (err) return void reject(err); if (err) return void reject(err);
resolve(); resolve();
}); });
@@ -260,11 +268,11 @@ export function updateDeviceTags(deviceId, tags): Promise<void> {
function putResource(resource, id, object): Promise<void> { function putResource(resource, id, object): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const collection = client const collection = client
.db() .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
collection.replaceOne({ _id: id }, object, { upsert: true }, err => { collection.replaceOne({ _id: id }, object, { upsert: true }, (err) => {
if (err) return void reject(err); if (err) return void reject(err);
resolve(); resolve();
}); });
@@ -273,14 +281,17 @@ function putResource(resource, id, object): Promise<void> {
}); });
} }
function deleteResource(resource, id): Promise<void> { function deleteResource(
resource: string,
id: string | ObjectID
): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const collection = client const collection = client
.db() .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
collection.deleteOne({ _id: id }, err => { collection.deleteOne({ _id: id }, (err) => {
if (err) return void reject(err); if (err) return void reject(err);
resolve(); resolve();
}); });
@@ -289,16 +300,22 @@ function deleteResource(resource, id): Promise<void> {
}); });
} }
export function putPreset(id, object): Promise<void> { export function putPreset(
id: string,
object: Record<string, unknown>
): Promise<void> {
object = mongodbFunctions.preProcessPreset(object); object = mongodbFunctions.preProcessPreset(object);
return putResource("presets", id, object); return putResource("presets", id, object);
} }
export function deletePreset(id): Promise<void> { export function deletePreset(id: string): Promise<void> {
return deleteResource("presets", id); return deleteResource("presets", id);
} }
export function putProvision(id, object): Promise<void> { export function putProvision(
id: string,
object: Record<string, unknown>
): Promise<void> {
if (!object.script) object.script = ""; if (!object.script) object.script = "";
try { try {
new Script(`"use strict";(function(){\n${object.script}\n})();`); new Script(`"use strict";(function(){\n${object.script}\n})();`);
@@ -308,11 +325,14 @@ export function putProvision(id, object): Promise<void> {
return putResource("provisions", id, object); return putResource("provisions", id, object);
} }
export function deleteProvision(id): Promise<void> { export function deleteProvision(id: string): Promise<void> {
return deleteResource("provisions", id); return deleteResource("provisions", id);
} }
export function putVirtualParameter(id, object): Promise<void> { export function putVirtualParameter(
id: string,
object: Record<string, unknown>
): Promise<void> {
if (!object.script) object.script = ""; if (!object.script) object.script = "";
try { try {
new Script(`"use strict";(function(){\n${object.script}\n})();`); new Script(`"use strict";(function(){\n${object.script}\n})();`);
@@ -322,37 +342,46 @@ export function putVirtualParameter(id, object): Promise<void> {
return putResource("virtualParameters", id, object); return putResource("virtualParameters", id, object);
} }
export function deleteVirtualParameter(id): Promise<void> { export function deleteVirtualParameter(id: string): Promise<void> {
return deleteResource("virtualParameters", id); return deleteResource("virtualParameters", id);
} }
export function putConfig(id, object): Promise<void> { export function putConfig(
id: string,
object: Record<string, unknown>
): Promise<void> {
return putResource("config", id, object); return putResource("config", id, object);
} }
export function deleteConfig(id): Promise<void> { export function deleteConfig(id: string): Promise<void> {
return deleteResource("config", id); return deleteResource("config", id);
} }
export function putPermission(id, object): Promise<void> { export function putPermission(
id: string,
object: Record<string, unknown>
): Promise<void> {
return putResource("permissions", id, object); return putResource("permissions", id, object);
} }
export function deletePermission(id): Promise<void> { export function deletePermission(id: string): Promise<void> {
return deleteResource("permissions", id); return deleteResource("permissions", id);
} }
export function putUser(id, object): Promise<void> { export function putUser(
id: string,
object: Record<string, unknown>
): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const collection = client.db().collection("users"); const collection = client.db().collection("users");
// update instead of replace to keep the password if not set by user // update instead of replace to keep the password if not set by user
collection.updateOne( collection.updateOne(
{ _id: id }, { _id: id },
{ $set: object }, { $set: object },
{ upsert: true }, { upsert: true },
err => { (err) => {
if (err) return void reject(err); if (err) return void reject(err);
resolve(); resolve();
} }
@@ -362,17 +391,21 @@ export function putUser(id, object): Promise<void> {
}); });
} }
export function deleteUser(id): Promise<void> { export function deleteUser(id: string): Promise<void> {
return deleteResource("users", id); return deleteResource("users", id);
} }
export function putFile(filename, metadata, contentStream): Promise<void> { export function putFile(
filename: string,
metadata: Record<string, string>,
contentStream: Readable
): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const bucket = new GridFSBucket(client.db()); const bucket = new GridFSBucket(client.db());
const uploadStream = bucket.openUploadStreamWithId(filename, filename, { const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
metadata: metadata metadata: metadata,
}); });
uploadStream.on("error", reject); uploadStream.on("error", reject);
contentStream.on("error", reject); contentStream.on("error", reject);
@@ -383,12 +416,12 @@ export function putFile(filename, metadata, contentStream): Promise<void> {
}); });
} }
export function deleteFile(filename): Promise<void> { export function deleteFile(filename: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then((client) => {
const bucket = new GridFSBucket(client.db()); const bucket = new GridFSBucket(client.db());
bucket.delete(filename, err => { bucket.delete(filename as any, (err) => {
if (err) return void reject(err); if (err) return void reject(err);
resolve(); resolve();
}); });
@@ -397,11 +430,11 @@ export function deleteFile(filename): Promise<void> {
}); });
} }
export function deleteFault(id): Promise<void> { export function deleteFault(id: string): Promise<void> {
return deleteResource("faults", id); return deleteResource("faults", id);
} }
export function deleteTask(id): Promise<void> { export function deleteTask(id: ObjectID): Promise<void> {
return deleteResource("tasks", id); return deleteResource("tasks", id);
} }
+2 -2
View File
@@ -46,7 +46,7 @@ export default class VersionedMap<K, V> {
return this._revision; return this._revision;
} }
public set revision(rev) { public set revision(rev: number) {
for (let i = this._sizeDiff.length; i <= rev; ++i) for (let i = this._sizeDiff.length; i <= rev; ++i)
this._sizeDiff[i] = this._sizeDiff[i - 1]; this._sizeDiff[i] = this._sizeDiff[i - 1];
@@ -193,7 +193,7 @@ export default class VersionedMap<K, V> {
const last = v[l]; const last = v[l];
v.splice(revision, l - revision); v.splice(revision, l - revision);
if (last === NONEXISTENT && !v.some(vv => vv !== NONEXISTENT)) if (last === NONEXISTENT && !v.some((vv) => vv !== NONEXISTENT))
this.map.delete(k); this.map.delete(k);
} }
} }
+8 -8
View File
@@ -90,7 +90,7 @@ export function parseAttrs(string: string): Attribute[] {
name: name, name: name,
namespace: namespace, namespace: namespace,
localName: localName, localName: localName,
value: value value: value,
}; };
attrs.push(e); attrs.push(e);
name = ""; name = "";
@@ -128,8 +128,8 @@ export function parseAttrs(string: string): Attribute[] {
return attrs; return attrs;
} }
export function decodeEntities(string): string { export function decodeEntities(string: string): string {
return string.replace(/&[0-9a-z#]+;/gi, match => { return string.replace(/&[0-9a-z#]+;/gi, (match) => {
switch (match) { switch (match) {
case "&quot;": case "&quot;":
return '"'; return '"';
@@ -161,15 +161,15 @@ export function decodeEntities(string): string {
}); });
} }
export function encodeEntities(string): string { export function encodeEntities(string: string): string {
const entities = { const entities = {
"&": "&amp;", "&": "&amp;",
'"': "&quot;", '"': "&quot;",
"'": "&apos;", "'": "&apos;",
"<": "&lt;", "<": "&lt;",
">": "&gt;" ">": "&gt;",
}; };
return string.replace(/[&"'<>]/g, m => entities[m]); return string.replace(/[&"'<>]/g, (m) => entities[m]);
} }
export function parseXml(string: string): Element { export function parseXml(string: string): Element {
@@ -186,7 +186,7 @@ export function parseXml(string: string): Element {
attrs: "", attrs: "",
text: "", text: "",
bodyIndex: 0, bodyIndex: 0,
children: [] children: [],
}; };
const stack: Element[] = [root]; const stack: Element[] = [root];
@@ -330,7 +330,7 @@ export function parseXml(string: string): Element {
: "", : "",
text: "", text: "",
bodyIndex: i + 1, bodyIndex: i + 1,
children: [] children: [],
}; };
parent.children.push(e); parent.children.push(e);
if (!selfClosing) stack.push(e); if (!selfClosing) stack.push(e);
+560 -447
View File
File diff suppressed because it is too large. Load diff
+9 -11
View File
@@ -46,28 +46,27 @@
"@types/parsimmon": "^1.10.0", "@types/parsimmon": "^1.10.0",
"@types/seedrandom": "^2.4.28", "@types/seedrandom": "^2.4.28",
"@types/svgo": "^1.2.1", "@types/svgo": "^1.2.1",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^1.13.0", "@typescript-eslint/parser": "^3.7.0",
"ava": "^2.2.0", "ava": "^2.2.0",
"codemirror": "^5.48.2", "codemirror": "^5.48.2",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"eslint": "^6.1.0", "eslint": "^7.5.0",
"eslint-config-prettier": "^6.0.0", "eslint-config-prettier": "^6.11.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"mithril": "^2.0.3", "mithril": "^2.0.3",
"postcss": "^7.0.17", "postcss": "^7.0.17",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"prettier": "^1.18.2", "prettier": "^2.0.5",
"rollup": "^1.17.0", "rollup": "^1.17.0",
"rollup-plugin-json": "^4.0.0", "rollup-plugin-json": "^4.0.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^5.1.1", "rollup-plugin-terser": "^5.1.1",
"rollup-plugin-typescript": "^1.0.1", "rollup-plugin-typescript": "^1.0.1",
"source-map-loader": "^0.2.4", "source-map-loader": "^0.2.4",
"svgo": "^1.3.0", "svgo": "^1.3.0",
"ts-node": "^8.3.0", "ts-node": "^8.10.2",
"typescript": "^3.5.3", "typescript": "^3.9.7",
"webpack": "^4.38.0" "webpack": "^4.38.0"
}, },
"engines": { "engines": {
@@ -75,11 +74,10 @@
}, },
"scripts": { "scripts": {
"test": "ava --verbose --color", "test": "ava --verbose --color",
"lint-ln": "find bin -type f -exec sh -c 'ln -fs \"$(sh -c pwd)/$1\" \"bin/.${1#bin/}.ts\"' _ {} \\;; find tools -type f -exec sh -c 'ln -fs \"$(sh -c pwd)/$1\" \"tools/.${1#tools/}.ts\"' _ {} \\;", "lint-eslint": "eslint --fix \"bin/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/*.ts\"",
"lint-eslint": "eslint --fix \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"",
"lint-prettier": "prettier --parser typescript --write \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"", "lint-prettier": "prettier --parser typescript --write \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"",
"lint-tsc": "tsc --noEmit", "lint-tsc": "tsc --noEmit",
"lint": "npm run lint-ln && npm run lint-eslint && npm run lint-prettier && npm run lint-tsc", "lint": "npm run lint-eslint && npm run lint-prettier && npm run lint-tsc",
"build": "node -r esm -r ts-node/register/transpile-only build/build" "build": "node -r esm -r ts-node/register/transpile-only build/build"
} }
} }
+2 -2
View File
@@ -2,7 +2,7 @@ import ava from "ava";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import * as auth from "../lib/auth"; import * as auth from "../lib/auth";
ava("digest", t => { ava("digest", (t) => {
const username = "test"; const username = "test";
const password = "test"; const password = "test";
const uri = "/"; const uri = "/";
@@ -14,7 +14,7 @@ ava("digest", t => {
const challenges = [ const challenges = [
`Digest realm="${realm}",nonce="${nonce}"`, `Digest realm="${realm}",nonce="${nonce}"`,
`Digest realm="${realm}",nonce="${nonce}",qop="auth"`, `Digest realm="${realm}",nonce="${nonce}",qop="auth"`,
`Digest realm="${realm}",nonce="${nonce}",qop="auth-int"` `Digest realm="${realm}",nonce="${nonce}",qop="auth-int"`,
]; ];
t.plan(challenges.length); t.plan(challenges.length);
+11 -38
View File
@@ -1,35 +1,7 @@
import ava from "ava"; import ava from "ava";
import * as common from "../lib/common"; import * as common from "../lib/common";
ava("typeOf", t => { ava("generateDeviceId", (t) => {
const cases = [
[undefined, common.UNDEFINED_TYPE],
[null, common.NULL_TYPE],
[true, common.BOOLEAN_TYPE],
[false, common.BOOLEAN_TYPE],
[0, common.NUMBER_TYPE],
[1, common.NUMBER_TYPE],
[-1, common.NUMBER_TYPE],
[0.0, common.NUMBER_TYPE],
[1.0, common.NUMBER_TYPE],
[1.1, common.NUMBER_TYPE],
[-1.0, common.NUMBER_TYPE],
[-1.1, common.NUMBER_TYPE],
["", common.STRING_TYPE],
["TEST", common.STRING_TYPE],
[[], common.ARRAY_TYPE],
[[1, 2, 3], common.ARRAY_TYPE],
[{}, common.OBJECT_TYPE],
[{ test: 1 }, common.OBJECT_TYPE],
[/[^A-Za-z0-9_]/g, common.REGEXP_TYPE],
[new RegExp("//"), common.REGEXP_TYPE],
[new Date(), common.DATE_TYPE]
];
t.plan(cases.length);
for (const c of cases) t.is(common.typeOf(c[0]), c[1]);
});
ava("generateDeviceId", t => {
const space = [" ", "%20"]; const space = [" ", "%20"];
const special = [";", "%3B"]; const special = [";", "%3B"];
const cases = [ const cases = [
@@ -37,30 +9,31 @@ ava("generateDeviceId", t => {
{ {
ProductClass: "TestProductClass", ProductClass: "TestProductClass",
OUI: "TestOUI", OUI: "TestOUI",
SerialNumber: "TestSerialNumber" SerialNumber: "TestSerialNumber",
}, },
"TestOUI-TestProductClass-TestSerialNumber" "TestOUI-TestProductClass-TestSerialNumber",
], ],
[ [
{ {
OUI: "TestOUI", OUI: "TestOUI",
SerialNumber: "TestSerialNumber" SerialNumber: "TestSerialNumber",
}, },
"TestOUI-TestSerialNumber" "TestOUI-TestSerialNumber",
], ],
[ [
{ {
OUI: `TestOUIWith${space[0]}_${special[0]}2912`, OUI: `TestOUIWith${space[0]}_${special[0]}2912`,
SerialNumber: `TestSerialNumberWith${space[0]}_${special[0]}2912` SerialNumber: `TestSerialNumberWith${space[0]}_${special[0]}2912`,
}, },
`TestOUIWith${space[1]}_${special[1]}2912-TestSerialNumberWith${space[1]}_${special[1]}2912` `TestOUIWith${space[1]}_${special[1]}2912-TestSerialNumberWith${space[1]}_${special[1]}2912`,
] ],
]; ];
t.plan(cases.length); t.plan(cases.length);
for (const c of cases) t.is(common.generateDeviceId(c[0]), c[1]); for (const c of cases)
t.is(common.generateDeviceId(c[0] as Record<string, string>), c[1]);
}); });
ava("escapeRegExp", t => { ava("escapeRegExp", (t) => {
t.is( t.is(
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"), common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
"\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]" "\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]"
+19 -19
View File
@@ -5,7 +5,7 @@ import PathSet from "../lib/common/path-set";
import VersionedMap from "../lib/versioned-map"; import VersionedMap from "../lib/versioned-map";
import { Attributes } from "../lib/types"; import { Attributes } from "../lib/types";
ava("getAliasDeclarations", t => { ava("getAliasDeclarations", (t) => {
const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b"); const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b");
const decs = device.getAliasDeclarations(path, 99); const decs = device.getAliasDeclarations(path, 99);
@@ -22,64 +22,64 @@ ava("getAliasDeclarations", t => {
} }
}); });
ava("unpack", t => { ava("unpack", (t) => {
const now = Date.now(); const now = Date.now();
const deviceData = { const deviceData = {
paths: new PathSet(), paths: new PathSet(),
timestamps: new VersionedMap<Path, number>(), timestamps: new VersionedMap<Path, number>(),
attributes: new VersionedMap<Path, Attributes>(), attributes: new VersionedMap<Path, Attributes>(),
trackers: new Map<Path, { [name: string]: number }>(), trackers: new Map<Path, { [name: string]: number }>(),
changes: new Set<string>() changes: new Set<string>(),
}; };
device.set(deviceData, Path.parse("a.1.b"), now, { device.set(deviceData, Path.parse("a.1.b"), now, {
value: [now, ["b", "xsd:string"]] value: [now, ["b", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.c"), now, { device.set(deviceData, Path.parse("a.1.c"), now, {
value: [now, ["c", "xsd:string"]] value: [now, ["c", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.1.a"), now, { device.set(deviceData, Path.parse("a.1.a.1.a"), now, {
value: [now, ["", "xsd:string"]] value: [now, ["", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.1.b"), now, { device.set(deviceData, Path.parse("a.1.a.1.b"), now, {
value: [now, ["b1", "xsd:string"]] value: [now, ["b1", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.1.c"), now, { device.set(deviceData, Path.parse("a.1.a.1.c"), now, {
value: [now, ["c1", "xsd:string"]] value: [now, ["c1", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.2.a"), now, { device.set(deviceData, Path.parse("a.1.a.2.a"), now, {
value: [now, ["", "xsd:string"]] value: [now, ["", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.2.b"), now, { device.set(deviceData, Path.parse("a.1.a.2.b"), now, {
value: [now, ["b2", "xsd:string"]] value: [now, ["b2", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.1.a.2.c"), now, { device.set(deviceData, Path.parse("a.1.a.2.c"), now, {
value: [now, ["c2", "xsd:string"]] value: [now, ["c2", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.b"), now, { device.set(deviceData, Path.parse("a.2.b"), now, {
value: [now, ["b", "xsd:string"]] value: [now, ["b", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.c"), now, { device.set(deviceData, Path.parse("a.2.c"), now, {
value: [now, ["c", "xsd:string"]] value: [now, ["c", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.1.a"), now, { device.set(deviceData, Path.parse("a.2.a.1.a"), now, {
value: [now, ["", "xsd:string"]] value: [now, ["", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.1.b"), now, { device.set(deviceData, Path.parse("a.2.a.1.b"), now, {
value: [now, ["b1", "xsd:string"]] value: [now, ["b1", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.1.c"), now, { device.set(deviceData, Path.parse("a.2.a.1.c"), now, {
value: [now, ["c1", "xsd:string"]] value: [now, ["c1", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.2.a"), now, { device.set(deviceData, Path.parse("a.2.a.2.a"), now, {
value: [now, ["", "xsd:string"]] value: [now, ["", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.2.b"), now, { device.set(deviceData, Path.parse("a.2.a.2.b"), now, {
value: [now, ["c1", "xsd:string"]] value: [now, ["c1", "xsd:string"]],
}); });
device.set(deviceData, Path.parse("a.2.a.2.c"), now, { device.set(deviceData, Path.parse("a.2.a.2.c"), now, {
value: [now, ["b1", "xsd:string"]] value: [now, ["b1", "xsd:string"]],
}); });
let unpacked: Path[]; let unpacked: Path[];
+8 -8
View File
@@ -2,7 +2,7 @@ import ava from "ava";
import * as mongodbFunctions from "../lib/mongodb-functions"; import * as mongodbFunctions from "../lib/mongodb-functions";
import { stringify } from "../lib/common/expression-parser"; import { stringify } from "../lib/common/expression-parser";
ava("mongoQueryToFilter", t => { ava("mongoQueryToFilter", (t) => {
const tests = [ const tests = [
[{}, "true"], [{}, "true"],
[{ test: "test" }, 'test = "test"'], [{ test: "test" }, 'test = "test"'],
@@ -22,21 +22,21 @@ ava("mongoQueryToFilter", t => {
[{ _tags: { $eq: "test" } }, "Tags.test IS NOT NULL"], [{ _tags: { $eq: "test" } }, "Tags.test IS NOT NULL"],
[ [
{ $and: [{ test: "test" }, { test: { $ne: "test" } }] }, { $and: [{ test: "test" }, { test: { $ne: "test" } }] },
'test = "test" AND test <> "test"' 'test = "test" AND test <> "test"',
], ],
[{ test: "test", test2: "test2" }, 'test = "test" AND test2 = "test2"'], [{ test: "test", test2: "test2" }, 'test = "test" AND test2 = "test2"'],
[ [
{ $or: [{ test: "test" }, { test: { $ne: "test" } }] }, { $or: [{ test: "test" }, { test: { $ne: "test" } }] },
'test = "test" OR test <> "test"' 'test = "test" OR test <> "test"',
], ],
[ [
{ test: { $gte: "test1", $ne: "test2" } }, { test: { $gte: "test1", $ne: "test2" } },
'test >= "test1" AND test <> "test2"' 'test >= "test1" AND test <> "test2"',
], ],
[ [
{ test: "test", test2: { $ne: "test2" } }, { test: "test", test2: { $ne: "test2" } },
'test = "test" AND test2 <> "test2"' 'test = "test" AND test2 <> "test2"',
] ],
]; ];
const shouldFailTests = [ const shouldFailTests = [
@@ -45,7 +45,7 @@ ava("mongoQueryToFilter", t => {
[{ "Tags.test": { $gt: true } }, "Invalid tag query"], [{ "Tags.test": { $gt: true } }, "Invalid tag query"],
[{ _tags: [] }, "Invalid type"], [{ _tags: [] }, "Invalid type"],
[{ _tags: { $gt: "test" } }, "Invalid tag query"], [{ _tags: { $gt: "test" } }, "Invalid tag query"],
[{ $nor: [] }, "Operator $nor not supported"] [{ $nor: [] }, "Operator $nor not supported"],
]; ];
t.plan(tests.length + 2 * shouldFailTests.length); t.plan(tests.length + 2 * shouldFailTests.length);
@@ -54,7 +54,7 @@ ava("mongoQueryToFilter", t => {
for (const test of shouldFailTests) { for (const test of shouldFailTests) {
const func = (): void => { const func = (): void => {
mongodbFunctions.mongoQueryToFilter(test[0]); mongodbFunctions.mongoQueryToFilter(test[0] as Record<string, unknown>);
}; };
const error = t.throws(func, Error); const error = t.throws(func, Error);
t.is(error.message, test[1]); t.is(error.message, test[1]);
+10 -10
View File
@@ -2,7 +2,7 @@ import ava from "ava";
import Path from "../lib/common/path"; import Path from "../lib/common/path";
import PathSet from "../lib/common/path-set"; import PathSet from "../lib/common/path-set";
ava("depth", t => { ava("depth", (t) => {
const pathSet = new PathSet(); const pathSet = new PathSet();
t.is(pathSet.depth, 0); t.is(pathSet.depth, 0);
pathSet.add(Path.parse("")); pathSet.add(Path.parse(""));
@@ -11,14 +11,14 @@ ava("depth", t => {
t.is(pathSet.depth, 2); t.is(pathSet.depth, 2);
}); });
ava("add", t => { ava("add", (t) => {
const pathSet = new PathSet(); const pathSet = new PathSet();
pathSet.add(Path.parse("a")); pathSet.add(Path.parse("a"));
pathSet.add(Path.parse("a")); pathSet.add(Path.parse("a"));
t.is(pathSet.find(Path.parse("a"), true, true, 99).length, 1); t.is(pathSet.find(Path.parse("a"), true, true, 99).length, 1);
}); });
ava("get", t => { ava("get", (t) => {
const pathSet = new PathSet(); const pathSet = new PathSet();
pathSet.add(Path.parse("a.*")); pathSet.add(Path.parse("a.*"));
pathSet.add(Path.parse("a.a")); pathSet.add(Path.parse("a.a"));
@@ -28,7 +28,7 @@ ava("get", t => {
t.is(pathSet.get(Path.parse("*.a")), null); t.is(pathSet.get(Path.parse("*.a")), null);
}); });
ava("find", t => { ava("find", (t) => {
const pathSet = new PathSet(); const pathSet = new PathSet();
pathSet.add(Path.parse("a")); pathSet.add(Path.parse("a"));
pathSet.add(Path.parse("a.*")); pathSet.add(Path.parse("a.*"));
@@ -37,32 +37,32 @@ ava("find", t => {
pathSet.add(Path.parse("*.*")); pathSet.add(Path.parse("*.*"));
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse(""), true, true, 1).map(p => p.toString()), pathSet.find(Path.parse(""), true, true, 1).map((p) => p.toString()),
["a"] ["a"]
); );
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse(""), false, false, 2).map(p => p.toString()), pathSet.find(Path.parse(""), false, false, 2).map((p) => p.toString()),
["a", "a.*", "a.a", "*.a", "*.*"] ["a", "a.*", "a.a", "*.a", "*.*"]
); );
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse("a.*"), false, false).map(p => p.toString()), pathSet.find(Path.parse("a.*"), false, false).map((p) => p.toString()),
["a.*"] ["a.*"]
); );
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse("a.*"), false, true).map(p => p.toString()), pathSet.find(Path.parse("a.*"), false, true).map((p) => p.toString()),
["a.*", "a.a"] ["a.*", "a.a"]
); );
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse("a.*"), true, false).map(p => p.toString()), pathSet.find(Path.parse("a.*"), true, false).map((p) => p.toString()),
["a.*", "*.*"] ["a.*", "*.*"]
); );
t.deepEqual( t.deepEqual(
pathSet.find(Path.parse("a.*"), true, true).map(p => p.toString()), pathSet.find(Path.parse("a.*"), true, true).map((p) => p.toString()),
["a.*", "a.a", "*.a", "*.*"] ["a.*", "a.a", "*.a", "*.*"]
); );
}); });
+3 -3
View File
@@ -1,13 +1,13 @@
import ava from "ava"; import ava from "ava";
import Path from "../lib/common/path"; import Path from "../lib/common/path";
ava("toString", t => { ava("toString", (t) => {
const path1 = Path.parse('abc.[ abc : 123 , 123 : abc , 123: " abc "].123'); const path1 = Path.parse('abc.[ abc : 123 , 123 : abc , 123: " abc "].123');
const path2 = Path.parse('abc.[123: " abc ",abc:123,123:abc].123'); const path2 = Path.parse('abc.[123: " abc ",abc:123,123:abc].123');
t.is(path1.toString(), path2.toString()); t.is(path1.toString(), path2.toString());
}); });
ava("slice", t => { ava("slice", (t) => {
const path = Path.parse("a.*.b.[x:y].c"); const path = Path.parse("a.*.b.[x:y].c");
const sliced = path.slice(1, -1); const sliced = path.slice(1, -1);
t.is(sliced.toString(), '*.b.[x:"y"]'); t.is(sliced.toString(), '*.b.[x:"y"]');
@@ -15,7 +15,7 @@ ava("slice", t => {
t.is(sliced.wildcard, 0b1); t.is(sliced.wildcard, 0b1);
}); });
ava("concat", t => { ava("concat", (t) => {
const path1 = Path.parse("a"); const path1 = Path.parse("a");
const path2 = Path.parse("*.[a:b]"); const path2 = Path.parse("*.[a:b]");
const concat = path1.concat(path2); const concat = path1.concat(path2);
+13 -13
View File
@@ -2,10 +2,10 @@ import ava from "ava";
import { import {
parseXmlDeclaration, parseXmlDeclaration,
decodeEntities, decodeEntities,
parseXml parseXml,
} from "../lib/xml-parser"; } from "../lib/xml-parser";
ava("parseXmlDeclaration", t => { ava("parseXmlDeclaration", (t) => {
const buf = Buffer.from( const buf = Buffer.from(
'<?xml version="1.0" encoding="UTF-8"?>\n<soap-env:Envelope />' '<?xml version="1.0" encoding="UTF-8"?>\n<soap-env:Envelope />'
); );
@@ -15,25 +15,25 @@ ava("parseXmlDeclaration", t => {
name: "version", name: "version",
namespace: "", namespace: "",
localName: "version", localName: "version",
value: "1.0" value: "1.0",
}, },
{ {
name: "encoding", name: "encoding",
namespace: "", namespace: "",
localName: "encoding", localName: "encoding",
value: "UTF-8" value: "UTF-8",
} },
]); ]);
}); });
ava("decodeEntities", t => { ava("decodeEntities", (t) => {
t.is( t.is(
decodeEntities("&&amp;&lt;&gt;&quot;&apos;&gt;&#167;&#xd842;&#xDFB7;;"), decodeEntities("&&amp;&lt;&gt;&quot;&apos;&gt;&#167;&#xd842;&#xDFB7;;"),
"&&<>\"'>§𠮷;" "&&<>\"'>§𠮷;"
); );
}); });
ava("parse", t => { ava("parse", (t) => {
const xml = const xml =
'<?xml version="1.0"?>\n<a-b:c><d f="1<g>"/><!-- comment --><h >i</h></a-b:c>'; '<?xml version="1.0"?>\n<a-b:c><d f="1<g>"/><!-- comment --><h >i</h></a-b:c>';
const parsed = parseXml(xml); const parsed = parseXml(xml);
@@ -60,7 +60,7 @@ ava("parse", t => {
attrs: 'f="1<g>"', attrs: 'f="1<g>"',
text: "", text: "",
bodyIndex: 42, bodyIndex: 42,
children: [] children: [],
}, },
{ {
name: "h", name: "h",
@@ -69,10 +69,10 @@ ava("parse", t => {
attrs: "", attrs: "",
text: "i", text: "i",
bodyIndex: 62, bodyIndex: 62,
children: [] children: [],
} },
] ],
} },
] ],
}); });
}); });
@@ -1,5 +1,3 @@
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
/** /**
* Copyright 2013-2019 GenieACS Inc. * Copyright 2013-2019 GenieACS Inc.
* *
@@ -60,7 +58,7 @@ connect()
.then(() => { .then(() => {
devicesCollection.findOne( devicesCollection.findOne(
{ {
_id: process.argv[2] _id: process.argv[2],
}, },
{}, {},
(err, device) => { (err, device) => {
@@ -79,6 +77,6 @@ connect()
} }
); );
}) })
.catch(err => { .catch((err) => {
throw err; throw err;
}); });
+2 -2
View File
@@ -14,11 +14,11 @@
"allowJs": true "allowJs": true
}, },
"include": [ "include": [
"./bin/.*.ts", "./bin/*.ts",
"./lib/**/*", "./lib/**/*",
"./ui/**/*", "./ui/**/*",
"./test/**/*", "./test/**/*",
"./build/**/*", "./build/**/*",
"./tools/.*.ts" "./tools/*.ts"
] ]
} }
+2 -2
View File
@@ -22,7 +22,7 @@ import { ClosureComponent, Component } from "mithril";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const active = { [vnode.attrs["page"]]: "active" }; const active = { [vnode.attrs["page"]]: "active" };
const tabs = []; const tabs = [];
@@ -97,7 +97,7 @@ const component: ClosureComponent = (): Component => {
} }
return m("nav#side-menu", m("ul", tabs)); return m("nav#side-menu", m("ul", tabs));
} },
}; };
}; };
+8 -8
View File
@@ -59,7 +59,7 @@ const adminPages = [
"files", "files",
"config", "config",
"users", "users",
"permissions" "permissions",
]; ];
let state; let state;
@@ -71,7 +71,7 @@ let fulfillTimeout = null;
function fulfill(): void { function fulfill(): void {
clearTimeout(fulfillTimeout); clearTimeout(fulfillTimeout);
fulfillTimeout = setTimeout(() => { fulfillTimeout = setTimeout(() => {
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => { store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then((updated) => {
if (updated) m.redraw(); if (updated) m.redraw();
}); });
}, 100); }, 100);
@@ -89,7 +89,7 @@ function pagify(pageName, page): RouteResolver {
attrs["page"] = pageName; attrs["page"] = pageName;
return m(layout, attrs, p); return m(layout, attrs, p);
}, },
onmatch: null onmatch: null,
}; };
component.onmatch = (args, requestedPath) => { component.onmatch = (args, requestedPath) => {
@@ -100,16 +100,16 @@ function pagify(pageName, page): RouteResolver {
return null; return null;
} }
return new Promise(resolve => { return new Promise((resolve) => {
page page
.init(args) .init(args)
.then(st => { .then((st) => {
if (!st) return void m.route.set("/"); if (!st) return void m.route.set("/");
state = st; state = st;
resolve(); resolve();
fulfill(); fulfill();
}) })
.catch(err => { .catch((err) => {
if (!window.username && err.message.indexOf("authorized") >= 0) { if (!window.username && err.message.indexOf("authorized") >= 0) {
notifications.push("error", err.message); notifications.push("error", err.message);
m.route.set("/login", { continue: requestedPath }); m.route.set("/login", { continue: requestedPath });
@@ -133,7 +133,7 @@ function redirectAdminPage(): RouteResolver {
} }
} }
return null; return null;
} },
}; };
return component; return component;
} }
@@ -155,5 +155,5 @@ m.route(document.body, "/overview", {
"/admin/files": pagify("files", filesPage), "/admin/files": pagify("files", filesPage),
"/admin/config": pagify("config", configPage), "/admin/config": pagify("config", configPage),
"/admin/users": pagify("users", usersPage), "/admin/users": pagify("users", usersPage),
"/admin/permissions": pagify("permissions", permissionsPage) "/admin/permissions": pagify("permissions", permissionsPage),
}); });
+10 -8
View File
@@ -17,11 +17,13 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
export default class Autocomplete { type AutocompleteCallback = (
private callback: (
value: string, value: string,
callback: (suggestions: { value: string; tip?: string }[]) => void callback: (suggestions: { value: string; tip?: string }[]) => void
) => void; ) => void;
export default class Autocomplete {
private callback: AutocompleteCallback;
private element: HTMLInputElement; private element: HTMLInputElement;
private hideTimeout: NodeJS.Timeout; private hideTimeout: NodeJS.Timeout;
private visible: boolean; private visible: boolean;
@@ -29,7 +31,7 @@ export default class Autocomplete {
private selection: number; private selection: number;
private container: HTMLElement; private container: HTMLElement;
public constructor(className, callback) { public constructor(className: string, callback: AutocompleteCallback) {
this.callback = callback; this.callback = callback;
this.element = null; this.element = null;
this.hideTimeout = null; this.hideTimeout = null;
@@ -44,7 +46,7 @@ export default class Autocomplete {
this.container.className = className; this.container.className = className;
} }
public attach(el): void { public attach(el: HTMLInputElement): void {
el.setAttribute("autocomplete", "off"); el.setAttribute("autocomplete", "off");
el.addEventListener("focus", () => { el.addEventListener("focus", () => {
@@ -62,7 +64,7 @@ export default class Autocomplete {
this.hide(); this.hide();
}); });
el.addEventListener("keydown", e => { el.addEventListener("keydown", (e) => {
if (this.element !== el) return; if (this.element !== el) return;
if (e.key === "Escape") { if (e.key === "Escape") {
if (this.visible) this.hide(); if (this.visible) this.hide();
@@ -108,7 +110,7 @@ export default class Autocomplete {
private update(): void { private update(): void {
const el = this.element; const el = this.element;
this.callback(el.value, suggestions => { this.callback(el.value, (suggestions) => {
if (this.element !== el) return; if (this.element !== el) return;
this.default = null; this.default = null;
@@ -154,7 +156,7 @@ export default class Autocomplete {
const t = document.createTextNode(suggestion.value); const t = document.createTextNode(suggestion.value);
e.appendChild(t); e.appendChild(t);
e.addEventListener("mousedown", ev => { e.addEventListener("mousedown", (ev) => {
ev.preventDefault(); ev.preventDefault();
el.value = suggestion.value; el.value = suggestion.value;
if (this.element === el) this.update(); if (this.element === el) this.update();
+14 -14
View File
@@ -24,7 +24,7 @@ import { m } from "./components";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const onPasswordChange = vnode.attrs["onPasswordChange"]; const onPasswordChange = vnode.attrs["onPasswordChange"];
const enforceAuth = !vnode.attrs["noAuth"]; const enforceAuth = !vnode.attrs["noAuth"];
const username = vnode.attrs["username"]; const username = vnode.attrs["username"];
@@ -40,19 +40,19 @@ const component: ClosureComponent = (): Component => {
type: "text", type: "text",
value: vnode.state["username"], value: vnode.state["username"],
disabled: !!username, disabled: !!username,
oninput: e => { oninput: (e) => {
vnode.state["username"] = e.target.value; vnode.state["username"] = e.target.value;
}, },
oncreate: _vnode => { oncreate: (_vnode) => {
(_vnode.dom as HTMLSelectElement).focus(); (_vnode.dom as HTMLSelectElement).focus();
} },
}) })
) ),
]; ];
let fields = { let fields = {
newPassword: "New password", newPassword: "New password",
confirmPassword: "Confirm password" confirmPassword: "Confirm password",
}; };
if (enforceAuth) if (enforceAuth)
fields = Object.assign({ authPassword: "Your password" }, fields); fields = Object.assign({ authPassword: "Your password" }, fields);
@@ -66,9 +66,9 @@ const component: ClosureComponent = (): Component => {
name: f, name: f,
type: "password", type: "password",
value: vnode.state[f], value: vnode.state[f],
oninput: e => { oninput: (e) => {
vnode.state[f] = e.target.value; vnode.state[f] = e.target.value;
} },
}) })
) )
); );
@@ -77,7 +77,7 @@ const component: ClosureComponent = (): Component => {
const submit = m( const submit = m(
"button.primary", "button.primary",
{ {
type: "submit" type: "submit",
}, },
"Change password" "Change password"
) as VnodeDOM; ) as VnodeDOM;
@@ -89,7 +89,7 @@ const component: ClosureComponent = (): Component => {
m( m(
"form", "form",
{ {
onsubmit: e => { onsubmit: (e) => {
e.redraw = false; e.redraw = false;
e.preventDefault(); e.preventDefault();
if ( if (
@@ -120,19 +120,19 @@ const component: ClosureComponent = (): Component => {
if (onPasswordChange) onPasswordChange(); if (onPasswordChange) onPasswordChange();
(submit.dom as HTMLFormElement).disabled = false; (submit.dom as HTMLFormElement).disabled = false;
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
(submit.dom as HTMLFormElement).disabled = false; (submit.dom as HTMLFormElement).disabled = false;
}); });
} }
} },
}, },
form form
) ),
]; ];
return m("div.put-form", children); return m("div.put-form", children);
} },
}; };
}; };
+7 -7
View File
@@ -23,11 +23,11 @@ import { codeMirror } from "./dynamic-loader";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
return m("textarea", { return m("textarea", {
name: vnode.attrs["id"], name: vnode.attrs["id"],
value: vnode.attrs["value"], value: vnode.attrs["value"],
oncreate: _vnode => { oncreate: (_vnode) => {
const editor = codeMirror.fromTextArea(_vnode.dom, { const editor = codeMirror.fromTextArea(_vnode.dom, {
mode: vnode.attrs["mode"], mode: vnode.attrs["mode"],
lineNumbers: true, lineNumbers: true,
@@ -40,21 +40,21 @@ const component: ClosureComponent = (): Component => {
"Cmd-Enter": () => { "Cmd-Enter": () => {
if (vnode.attrs["onSubmit"]) if (vnode.attrs["onSubmit"])
vnode.attrs["onSubmit"](_vnode.dom); vnode.attrs["onSubmit"](_vnode.dom);
} },
} },
}); });
if (vnode.attrs["onChange"]) { if (vnode.attrs["onChange"]) {
editor.on("change", e => { editor.on("change", (e) => {
vnode.attrs["onChange"](e.getValue()); vnode.attrs["onChange"](e.getValue());
}); });
} }
if (vnode.attrs["focus"]) editor.focus(); if (vnode.attrs["focus"]) editor.focus();
if (vnode.attrs["onReady"]) vnode.attrs["onReady"](editor); if (vnode.attrs["onReady"]) vnode.attrs["onReady"](editor);
} },
}); });
} },
}; };
}; };
+6 -6
View File
@@ -24,7 +24,7 @@ import m, {
ComponentTypes, ComponentTypes,
Lifecycle, Lifecycle,
ClosureComponent, ClosureComponent,
Vnode Vnode,
} from "mithril"; } from "mithril";
import parameter from "./components/parameter"; import parameter from "./components/parameter";
import parameterList from "./components/parameter-list"; import parameterList from "./components/parameter-list";
@@ -53,7 +53,7 @@ const comps = {
tags, tags,
ping, ping,
"device-link": deviceLink, "device-link": deviceLink,
"long-text": longTextComponent "long-text": longTextComponent,
}; };
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>(); const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
@@ -96,7 +96,7 @@ const M = new Proxy(m, {
get: (target, prop) => { get: (target, prop) => {
if (prop === "context") return contextFn; if (prop === "context") return contextFn;
else return Reflect.get(target, prop); else return Reflect.get(target, prop);
} },
}) as MC; }) as MC;
function contextFn(context, ...argumentsList): Vnode { function contextFn(context, ...argumentsList): Vnode {
@@ -125,17 +125,17 @@ export function contextifyComponent(component: ComponentTypes): ComponentTypes {
if (typeof component !== "function") { if (typeof component !== "function") {
c = Object.assign({}, component); c = Object.assign({}, component);
const view = component.view; const view = component.view;
c.view = function(vnode) { c.view = function (vnode) {
const context = vnodeContext.get(vnode) || {}; const context = vnodeContext.get(vnode) || {};
const res = Reflect.apply(view, this, [vnode]); const res = Reflect.apply(view, this, [vnode]);
applyContext(res, context); applyContext(res, context);
return res; return res;
}; };
} else if (!component.prototype || !component.prototype.view) { } else if (!component.prototype || !component.prototype.view) {
c = initialNode => { c = (initialNode) => {
const state = (component as ClosureComponent)(initialNode); const state = (component as ClosureComponent)(initialNode);
const view = state.view; const view = state.view;
state.view = function(vnode) { state.view = function (vnode) {
const context = vnodeContext.get(vnode) || {}; const context = vnodeContext.get(vnode) || {};
const res = Reflect.apply(view, this, [vnode]); const res = Reflect.apply(view, this, [vnode]);
applyContext(res, context); applyContext(res, context);
+16 -14
View File
@@ -32,31 +32,33 @@ function escapeRegExp(str): string {
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const search = m("input", { const search = m("input", {
type: "text", type: "text",
placeholder: "Search parameters", placeholder: "Search parameters",
oninput: e => { oninput: (e) => {
vnode.state["searchString"] = e.target.value; vnode.state["searchString"] = e.target.value;
e.redraw = false; e.redraw = false;
clearTimeout(vnode.state["timeout"]); clearTimeout(vnode.state["timeout"]);
vnode.state["timeout"] = setTimeout(m.redraw, 500); vnode.state["timeout"] = setTimeout(m.redraw, 500);
} },
}); });
const instanceRegex = /\.[0-9]+$/; const instanceRegex = /\.[0-9]+$/;
let re; let re;
if (vnode.state["searchString"]) { if (vnode.state["searchString"]) {
const keywords = vnode.state["searchString"].split(" ").filter(s => s); const keywords = vnode.state["searchString"]
.split(" ")
.filter((s) => s);
if (keywords.length) if (keywords.length)
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i"); re = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
} }
const rows = Object.keys(device) const rows = Object.keys(device)
.sort() .sort()
.map(k => { .map((k) => {
const p = device[k]; const p = device[k];
const val = []; const val = [];
const attrs = {}; const attrs = {};
@@ -83,9 +85,9 @@ const component: ClosureComponent = (): Component => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "deleteObject", name: "deleteObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0],
objectName: k objectName: k,
}); });
} },
}, },
getIcon("delete-instance") getIcon("delete-instance")
) )
@@ -100,9 +102,9 @@ const component: ClosureComponent = (): Component => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "addObject", name: "addObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0],
objectName: k objectName: k,
}); });
} },
}, },
getIcon("add-instance") getIcon("add-instance")
) )
@@ -119,9 +121,9 @@ const component: ClosureComponent = (): Component => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "getParameterValues", name: "getParameterValues",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0],
parameterNames: [k] parameterNames: [k],
}); });
} },
}, },
getIcon("refresh") getIcon("refresh")
) )
@@ -144,14 +146,14 @@ const component: ClosureComponent = (): Component => {
device["DeviceID.ID"].value[0] device["DeviceID.ID"].value[0]
)}.csv`, )}.csv`,
download: "", download: "",
style: "float: right;" style: "float: right;",
}, },
"Download" "Download"
), ),
search, search,
m(".parameter-list", m("table", m("tbody", rows))) m(".parameter-list", m("table", m("tbody", rows)))
); );
} },
}; };
}; };
+3 -3
View File
@@ -23,7 +23,7 @@ import * as store from "../store";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
if (vnode.attrs["filter"]) { if (vnode.attrs["filter"]) {
if ( if (
!store.evaluateExpression( !store.evaluateExpression(
@@ -34,13 +34,13 @@ const component: ClosureComponent = (): Component => {
return null; return null;
} }
const children = Object.values(vnode.attrs["components"]).map(c => { const children = Object.values(vnode.attrs["components"]).map((c) => {
if (typeof c !== "object") return `${c}`; if (typeof c !== "object") return `${c}`;
return m(c["type"], c); return m(c["type"], c);
}); });
if (vnode.attrs["element"]) return m(vnode.attrs["element"], children); if (vnode.attrs["element"]) return m(vnode.attrs["element"], children);
else return children; else return children;
} },
}; };
}; };
+10 -10
View File
@@ -25,7 +25,7 @@ import * as store from "../store";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const buttons = []; const buttons = [];
@@ -38,9 +38,9 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "reboot", name: "reboot",
device: device["DeviceID.ID"].value[0] device: device["DeviceID.ID"].value[0],
}); });
} },
}, },
"Reboot" "Reboot"
) )
@@ -54,9 +54,9 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "factoryReset", name: "factoryReset",
device: device["DeviceID.ID"].value[0] device: device["DeviceID.ID"].value[0],
}); });
} },
}, },
"Reset" "Reset"
) )
@@ -70,9 +70,9 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.stageDownload({ taskQueue.stageDownload({
name: "download", name: "download",
device: device["DeviceID.ID"].value[0] device: device["DeviceID.ID"].value[0],
}); });
} },
}, },
"Push file" "Push file"
) )
@@ -93,17 +93,17 @@ const component: ClosureComponent = (): Component => {
notifications.push("success", `${deviceId}: Device deleted`); notifications.push("success", `${deviceId}: Device deleted`);
m.route.set("/devices"); m.route.set("/devices");
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
}); });
} },
}, },
"Delete" "Delete"
) )
); );
return m(".actions-bar", buttons); return m(".actions-bar", buttons);
} },
}; };
}; };
+13 -10
View File
@@ -25,13 +25,13 @@ import { getIcon } from "../icons";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const deviceId = device["DeviceID.ID"].value[0]; const deviceId = device["DeviceID.ID"].value[0];
const faults = store.fetch("faults", [ const faults = store.fetch("faults", [
"AND", "AND",
[">", ["PARAM", "_id"], `${deviceId}:`], [">", ["PARAM", "_id"], `${deviceId}:`],
["<", ["PARAM", "_id"], `${deviceId}:zzzz`] ["<", ["PARAM", "_id"], `${deviceId}:zzzz`],
]); ]);
const headers = [ const headers = [
@@ -39,8 +39,8 @@ const component: ClosureComponent = (): Component => {
"Code", "Code",
"Message", "Message",
"Retries", "Retries",
"Timestamp" "Timestamp",
].map(l => m("th", l)); ].map((l) => m("th", l));
const thead = m("thead", m("tr", headers)); const thead = m("thead", m("tr", headers));
const rows = []; const rows = [];
@@ -57,7 +57,7 @@ const component: ClosureComponent = (): Component => {
"button", "button",
{ {
title: "Delete fault", title: "Delete fault",
onclick: e => { onclick: (e) => {
e.redraw = false; e.redraw = false;
store store
.deleteResource("faults", f["_id"]) .deleteResource("faults", f["_id"])
@@ -66,21 +66,24 @@ const component: ClosureComponent = (): Component => {
store.fulfill(Date.now(), Date.now()); store.fulfill(Date.now(), Date.now());
m.redraw(); m.redraw();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(Date.now(), Date.now()); store.fulfill(Date.now(), Date.now());
}); });
} },
}, },
getIcon("remove") getIcon("remove")
) )
) ),
]); ]);
} }
let tbody; let tbody;
if (rows.length) { if (rows.length) {
tbody = m("tbody", rows.map(r => m("tr", r))); tbody = m(
"tbody",
rows.map((r) => m("tr", r))
);
} else { } else {
tbody = m( tbody = m(
"tbody", "tbody",
@@ -89,7 +92,7 @@ const component: ClosureComponent = (): Component => {
} }
return m("table.table", thead, tbody); return m("table.table", thead, tbody);
} },
}; };
}; };
+3 -3
View File
@@ -22,12 +22,12 @@ import { m } from "../components";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
let deviceId; let deviceId;
if (vnode.attrs["device"]) if (vnode.attrs["device"])
deviceId = vnode.attrs["device"]["DeviceID.ID"].value[0]; 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}`; if (typeof c !== "object") return `${c}`;
const attrs = Object.assign({}, vnode.attrs, c); const attrs = Object.assign({}, vnode.attrs, c);
return m(attrs["type"], attrs); return m(attrs["type"], attrs);
@@ -41,7 +41,7 @@ const component: ClosureComponent = (): Component => {
} else { } else {
return children; return children;
} }
} },
}; };
}; };
+5 -5
View File
@@ -26,9 +26,9 @@ const CHARTS = config.ui.overview.charts;
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const chart = CHARTS[vnode.attrs["chart"]]; const chart = CHARTS[vnode.attrs["chart"]] as Record<string, unknown>;
if (!chart) return null; if (!chart) return null;
for (const slice of Object.values(chart.slices)) { for (const slice of Object.values(chart.slices)) {
const filter = slice["filter"]; const filter = slice["filter"];
@@ -39,20 +39,20 @@ const component: ClosureComponent = (): Component => {
width: "1em", width: "1em",
height: "1em", height: "1em",
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink" "xmlns:xlink": "http://www.w3.org/1999/xlink",
}, },
m("circle", { m("circle", {
cx: "0.5em", cx: "0.5em",
cy: "0.5em", cy: "0.5em",
r: "0.4em", 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; return null;
} },
}; };
}; };
+6 -6
View File
@@ -22,14 +22,14 @@ import { m } from "../components";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const rows = Object.values(vnode.attrs["parameters"]).map(parameter => { const rows = Object.values(vnode.attrs["parameters"]).map((parameter) => {
const p = m.context( const p = m.context(
{ {
device: device, device: device,
parameter: parameter["parameter"] parameter: parameter["parameter"],
}, },
parameter["type"] || "parameter", parameter["type"] || "parameter",
parameter parameter
@@ -38,11 +38,11 @@ const component: ClosureComponent = (): Component => {
return m( return m(
"tr", "tr",
{ {
onupdate: vn => { onupdate: (vn) => {
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom (vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
? "" ? ""
: "none"; : "none";
} },
}, },
m("th", parameter["label"]), m("th", parameter["label"]),
m("td", p) m("td", p)
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
}); });
return m("table.parameter-list", rows); return m("table.parameter-list", rows);
} },
}; };
}; };
+15 -15
View File
@@ -26,21 +26,21 @@ import { getIcon } from "../icons";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
oninit: vnode => { oninit: (vnode) => {
const obj = vnode.attrs["parameter"]; const obj = vnode.attrs["parameter"];
if (!Array.isArray(obj) || obj[0] !== "PARAM") if (!Array.isArray(obj) || obj[0] !== "PARAM")
throw new Error("Object must be a parameter path"); throw new Error("Object must be a parameter path");
vnode.state["object"] = obj[1]; vnode.state["object"] = obj[1];
vnode.state["parameters"] = Object.values(vnode.attrs["childParameters"]); vnode.state["parameters"] = Object.values(vnode.attrs["childParameters"]);
}, },
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const object = store.evaluateExpression(vnode.state["object"], device); const object = store.evaluateExpression(vnode.state["object"], device);
const parameters = vnode.state["parameters"]; const parameters = vnode.state["parameters"];
if (!device[object]) return null; if (typeof object !== "string" || !device[object]) return null;
const instances = new Set(); const instances: Set<string> = new Set();
const prefix = `${object}.`; const prefix = `${object}.`;
for (const p in device) { for (const p in device) {
if (p.startsWith(prefix)) { if (p.startsWith(prefix)) {
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
} }
} }
const headers = Object.values(parameters).map(p => m("th", p["label"])); const headers = Object.values(parameters).map((p) => m("th", p["label"]));
const thead = m("thead", m("tr", headers)); const thead = m("thead", m("tr", headers));
@@ -59,7 +59,7 @@ const component: ClosureComponent = (): Component => {
let filter = let filter =
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true; vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
filter = expressionParser.map(filter, e => { filter = expressionParser.map(filter, (e) => {
if (Array.isArray(e) && e[0] === "PARAM") if (Array.isArray(e) && e[0] === "PARAM")
return ["PARAM", ["||", i, ".", e[1]]]; return ["PARAM", ["||", i, ".", e[1]]];
return e; return e;
@@ -67,8 +67,8 @@ const component: ClosureComponent = (): Component => {
if (!store.evaluateExpression(filter, device)) continue; if (!store.evaluateExpression(filter, device)) continue;
const row = parameters.map(p => { const row = parameters.map((p) => {
const param = expressionParser.map(p.parameter, e => { const param = expressionParser.map(p.parameter, (e) => {
if (Array.isArray(e) && e[0] === "PARAM") if (Array.isArray(e) && e[0] === "PARAM")
return ["PARAM", ["||", i, ".", e[1]]]; return ["PARAM", ["||", i, ".", e[1]]];
return e; return e;
@@ -78,13 +78,13 @@ const component: ClosureComponent = (): Component => {
m.context( m.context(
{ {
device: device, device: device,
parameter: param parameter: param,
}, },
p.type || "parameter", p.type || "parameter",
Object.assign({}, p, { Object.assign({}, p, {
device: device, device: device,
parameter: param, parameter: param,
label: null label: null,
}) })
) )
); );
@@ -102,9 +102,9 @@ const component: ClosureComponent = (): Component => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "deleteObject", name: "deleteObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0],
objectName: i objectName: i,
}); });
} },
}, },
getIcon("delete-instance") getIcon("delete-instance")
) )
@@ -135,9 +135,9 @@ const component: ClosureComponent = (): Component => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "addObject", name: "addObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0],
objectName: object objectName: object,
}); });
} },
}, },
getIcon("add-instance") getIcon("add-instance")
) )
@@ -150,7 +150,7 @@ const component: ClosureComponent = (): Component => {
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))]; return [label, m("table.table", thead, m("tbody", rows))];
} },
}; };
}; };
+9 -9
View File
@@ -29,7 +29,7 @@ import { getIcon } from "../icons";
const evaluateParam = memoize((exp, obj, now: number) => { const evaluateParam = memoize((exp, obj, now: number) => {
let timestamp = now; let timestamp = now;
const params = new Set(); const params = new Set();
const value = expression.evaluate(exp, obj, now, e => { const value = expression.evaluate(exp, obj, now, (e) => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
if (e[0] === "PARAM") { if (e[0] === "PARAM") {
params.add(e[1]); params.add(e[1]);
@@ -52,7 +52,7 @@ const evaluateParam = memoize((exp, obj, now: number) => {
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
const { value, timestamp, parameter } = evaluateParam( const { value, timestamp, parameter } = evaluateParam(
@@ -77,11 +77,11 @@ const component: ClosureComponent = (): Component => {
[ [
parameter, parameter,
device[parameter].value[0], device[parameter].value[0],
device[parameter].value[1] device[parameter].value[1],
] ],
] ],
}); });
} },
}, },
getIcon("edit") getIcon("edit")
); );
@@ -93,7 +93,7 @@ const component: ClosureComponent = (): Component => {
"span", "span",
{ {
class: "parameter-value", class: "parameter-value",
onmouseover: e => { onmouseover: (e) => {
e.redraw = false; e.redraw = false;
// Don't update any child element // Don't update any child element
if (e.target === (el as VnodeDOM).dom) { if (e.target === (el as VnodeDOM).dom) {
@@ -101,12 +101,12 @@ const component: ClosureComponent = (): Component => {
const localeString = new Date(timestamp).toLocaleString(); const localeString = new Date(timestamp).toLocaleString();
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`; e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
} }
} },
}, },
el, el,
edit edit
); );
} },
}; };
}; };
+2 -2
View File
@@ -50,7 +50,7 @@ const component: ClosureComponent = (vn): Component => {
onremove: () => { onremove: () => {
clearInterval(interval); clearInterval(interval);
}, },
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
let param = let param =
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]; device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
@@ -73,7 +73,7 @@ const component: ClosureComponent = (vn): Component => {
} }
return m("div", `Pinging ${host}:`); return m("div", `Pinging ${host}:`);
} },
}; };
}; };
+8 -8
View File
@@ -25,26 +25,26 @@ import * as notifications from "../notifications";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
return m( return m(
"button.primary", "button.primary",
{ {
title: "Initiate session and refresh basic parameters", title: "Initiate session and refresh basic parameters",
onclick: e => { onclick: (e) => {
e.target.disabled = true; e.target.disabled = true;
const params = Object.values(vnode.attrs["parameters"]) const params = Object.values(vnode.attrs["parameters"])
.map(exp => { .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); return store.evaluateExpression(exp[1], device) as string;
}) })
.filter(exp => !!exp); .filter((exp) => !!exp);
const task = { const task = {
name: "getParameterValues", name: "getParameterValues",
parameterNames: params, parameterNames: params,
device: device["DeviceID.ID"].value[0] device: device["DeviceID.ID"].value[0],
}; };
taskQueue taskQueue
@@ -80,11 +80,11 @@ const component: ClosureComponent = (): Component => {
e.target.disabled = false; e.target.disabled = false;
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Summon" "Summon"
); );
} },
}; };
}; };
+15 -10
View File
@@ -25,7 +25,7 @@ import { getIcon } from "../icons";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs["device"];
let writable = true; let writable = true;
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"]; if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
@@ -36,18 +36,23 @@ const component: ClosureComponent = (): Component => {
tags.sort(); tags.sort();
if (!writable) return m(".tags", tags.map(t => m("span.tag", t))); if (!writable) {
return m(
".tags",
tags.map((t) => m("span.tag", t))
);
}
return m( return m(
".tags", ".tags",
tags.map(tag => tags.map((tag) =>
m( m(
"span.tag", "span.tag",
tag, tag,
m( m(
"button", "button",
{ {
onclick: e => { onclick: (e) => {
e.target.disabled = true; e.target.disabled = true;
const deviceId = device["DeviceID.ID"].value[0]; const deviceId = device["DeviceID.ID"].value[0];
store store
@@ -60,14 +65,14 @@ const component: ClosureComponent = (): Component => {
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
e.target.disabled = false; e.target.disabled = false;
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${err.message}` `${deviceId}: ${err.message}`
); );
}); });
} },
}, },
getIcon("remove") getIcon("remove")
) )
@@ -79,7 +84,7 @@ const component: ClosureComponent = (): Component => {
m( m(
"button", "button",
{ {
onclick: e => { onclick: (e) => {
e.target.disabled = true; e.target.disabled = true;
const deviceId = device["DeviceID.ID"].value[0]; const deviceId = device["DeviceID.ID"].value[0];
const tag = prompt(`Enter tag to assign to device:`); const tag = prompt(`Enter tag to assign to device:`);
@@ -95,17 +100,17 @@ const component: ClosureComponent = (): Component => {
notifications.push("success", `${deviceId}: Tags updated`); notifications.push("success", `${deviceId}: Tags updated`);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
e.target.disabled = false; e.target.disabled = false;
notifications.push("error", `${deviceId}: ${err.message}`); notifications.push("error", `${deviceId}: ${err.message}`);
}); });
} },
}, },
getIcon("add") getIcon("add")
) )
) )
); );
} },
}; };
}; };
+7 -4
View File
@@ -27,7 +27,7 @@ interface Diff {
remove: string[]; remove: string[];
} }
export function flattenConfig(config): any { export function flattenConfig(config: Record<string, unknown>): any {
const flatten = {}; const flatten = {};
const recuresive = (obj: any, root: string): void => { const recuresive = (obj: any, root: string): void => {
for (const [k, v] of Object.entries(obj)) { for (const [k, v] of Object.entries(obj)) {
@@ -56,7 +56,7 @@ export function structureConfig(config: Config[]): any {
ref[keys[0]] = c.value; ref[keys[0]] = c.value;
} }
const toArray = function(object): any { const toArray = function (object): any {
const MAX_BITS = 30; const MAX_BITS = 30;
const MAX_ARRAY_SIZE = MAX_BITS * 10; const MAX_ARRAY_SIZE = MAX_BITS * 10;
@@ -96,10 +96,13 @@ export function structureConfig(config: Config[]): any {
return toArray(_config); return toArray(_config);
} }
export function diffConfig(current, target): Diff { export function diffConfig(
current: Record<string, unknown>,
target: Record<string, unknown>
): Diff {
const diff = { const diff = {
add: [], add: [],
remove: [] remove: [],
}; };
for (const [k, v] of Object.entries(target)) for (const [k, v] of Object.entries(target))
+30 -30
View File
@@ -30,7 +30,7 @@ import { getIcon } from "./icons";
const attributes = [ const attributes = [
{ id: "_id", label: "Key" }, { id: "_id", label: "Key" },
{ id: "value", label: "Value", type: "textarea" } { id: "value", label: "Value", type: "textarea" },
]; ];
interface ValidationErrors { interface ValidationErrors {
@@ -52,13 +52,13 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
object.value = stringify(parse(object.value || "")); object.value = stringify(parse(object.value || ""));
} catch (err) { } catch (err) {
return void resolve({ return void resolve({
value: "Config value must be valid expression" value: "Config value must be valid expression",
}); });
} }
store store
.resourceExists("config", id) .resourceExists("config", id)
.then(exists => { .then((exists) => {
if (exists && isNew) { if (exists && isNew) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return void resolve({ _id: "Config already exists" }); return void resolve({ _id: "Config already exists" });
@@ -90,7 +90,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
reject(err); reject(err);
}); });
@@ -102,14 +102,14 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
const formData = { const formData = {
resource: "config", resource: "config",
attributes: attributes attributes: attributes,
}; };
function escapeRegExp(str): string { function escapeRegExp(str): string {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&"); return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
} }
export function init(): Promise<{}> { export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("config", 2)) { if (!window.authorizer.hasAccess("config", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -131,9 +131,9 @@ function renderTable(confsResponse, searchString): Children {
let regex; let regex;
if (searchString) { if (searchString) {
const keywords = searchString.split(" ").filter(s => s); const keywords = searchString.split(" ").filter((s) => s);
if (keywords.length) if (keywords.length)
regex = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i"); regex = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
} }
const rows = []; const rows = [];
@@ -154,9 +154,9 @@ function renderTable(confsResponse, searchString): Children {
{ {
base: conf, base: conf,
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, false) putActionHandler(action, object, false)
.then(errors => { .then((errors) => {
const ErrorList = errors ? Object.values(errors) : []; const ErrorList = errors ? Object.values(errors) : [];
if (ErrorList.length) { if (ErrorList.length) {
for (const err of ErrorList) for (const err of ErrorList)
@@ -166,19 +166,19 @@ function renderTable(confsResponse, searchString): Children {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
getIcon("edit") getIcon("edit")
); );
@@ -190,10 +190,10 @@ function renderTable(confsResponse, searchString): Children {
onclick: () => { onclick: () => {
if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return; if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return;
putActionHandler("delete", conf).catch(err => { putActionHandler("delete", conf).catch((err) => {
throw err; throw err;
}); });
} },
}, },
getIcon("remove") getIcon("remove")
); );
@@ -219,18 +219,18 @@ function renderTable(confsResponse, searchString): Children {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Config - GenieACS"; document.title = "Config - GenieACS";
const search = m("input", { const search = m("input", {
type: "text", type: "text",
placeholder: "Search config", placeholder: "Search config",
oninput: e => { oninput: (e) => {
vnode.state["searchString"] = e.target.value; vnode.state["searchString"] = e.target.value;
e.redraw = false; e.redraw = false;
clearTimeout(vnode.state["timeout"]); clearTimeout(vnode.state["timeout"]);
vnode.state["timeout"] = setTimeout(m.redraw, 250); vnode.state["timeout"] = setTimeout(m.redraw, 250);
} },
}); });
const confs = store.fetch("config", true); const confs = store.fetch("config", true);
@@ -250,9 +250,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign( Object.assign(
{ {
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, true) putActionHandler(action, object, true)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -264,19 +264,19 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"New config" "New config"
); );
@@ -286,7 +286,7 @@ export const component: ClosureComponent = (): Component => {
{ name: "charts", prefix: "ui.overview.charts.", data: [] }, { name: "charts", prefix: "ui.overview.charts.", data: [] },
{ name: "filters", prefix: "ui.filters.", data: [] }, { name: "filters", prefix: "ui.filters.", data: [] },
{ name: "index page", prefix: "ui.index.", data: [] }, { name: "index page", prefix: "ui.index.", data: [] },
{ name: "device page", prefix: "ui.device.", data: [] } { name: "device page", prefix: "ui.device.", data: [] },
]; ];
if (confs.fulfilled) { if (confs.fulfilled) {
@@ -312,7 +312,7 @@ export const component: ClosureComponent = (): Component => {
uiConfigComponent, uiConfigComponent,
Object.assign( Object.assign(
{ {
onUpdate: errs => { onUpdate: (errs: string[]) => {
const errors = errs ? Object.values(errs) : []; const errors = errs ? Object.values(errs) : [];
if (errors.length) { if (errors.length) {
for (const err of errors) for (const err of errors)
@@ -329,18 +329,18 @@ export const component: ClosureComponent = (): Component => {
} }
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}, },
onError: err => { onError: (err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
overlay.close(cb); overlay.close(cb);
} },
}, },
attrs attrs
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
`Edit ${sub.name}` `Edit ${sub.name}`
) )
@@ -359,8 +359,8 @@ export const component: ClosureComponent = (): Component => {
renderTable(confs, vnode.state["searchString"]) renderTable(confs, vnode.state["searchString"])
), ),
m(".actions-bar", [newConfig].concat(subs)) m(".actions-bar", [newConfig].concat(subs))
) ),
]; ];
} },
}; };
}; };
+6 -4
View File
@@ -22,7 +22,9 @@ import { m } from "./components";
import config from "./config"; import config from "./config";
import * as store from "./store"; import * as store from "./store";
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 2)) { if (!window.authorizer.hasAccess("devices", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -31,13 +33,13 @@ export function init(args): Promise<{}> {
return Promise.resolve({ return Promise.resolve({
deviceId: args.id, deviceId: args.id,
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id] deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id],
}); });
} }
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { 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;
@@ -49,6 +51,6 @@ export const component: ClosureComponent = (): Component => {
cmps.push(m.context({ device: dev[0] }, c["type"], c)); cmps.push(m.context({ device: dev[0] }, c["type"], c));
return [m("h1", vnode.attrs["deviceId"]), cmps]; return [m("h1", vnode.attrs["deviceId"]), cmps];
} },
}; };
}; };
+24 -22
View File
@@ -35,7 +35,7 @@ const PAGE_SIZE = config.ui.pageSize || 10;
const memoizedParse = memoize(parse); const memoizedParse = memoize(parse);
const memoizedJsonParse = memoize(JSON.parse); const memoizedJsonParse = memoize(JSON.parse);
const memoizedGetSortable = memoize(p => { const memoizedGetSortable = memoize((p) => {
const expressionParams = extractParams(p); const expressionParams = extractParams(p);
if (expressionParams.length === 1) { if (expressionParams.length === 1) {
const param = evaluate(expressionParams[0]); const param = evaluate(expressionParams[0]);
@@ -49,19 +49,21 @@ const getDownloadUrl = memoize((filter, indexParameters) => {
for (const p of indexParameters) columns[p.label] = stringify(p.parameter); for (const p of indexParameters) columns[p.label] = stringify(p.parameter);
return `api/devices.csv?${m.buildQueryString({ return `api/devices.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(columns) columns: JSON.stringify(columns),
})}`; })}`;
}); });
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return expressionParser.map(query, e => { return expressionParser.map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("devices", e[2], e[3]); return smartQuery.unpack("devices", e[2], e[3]);
return e; return e;
}); });
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!window.authorizer.hasAccess("devices", 2)) if (!window.authorizer.hasAccess("devices", 2))
return void reject(new Error("You are not authorized to view this page")); return void reject(new Error("You are not authorized to view this page"));
@@ -72,14 +74,14 @@ export function init(args): Promise<{}> {
if (!indexParameters.length) { if (!indexParameters.length) {
indexParameters.push({ indexParameters.push({
label: "ID", label: "ID",
parameter: ["PARAM", "DeviceID.ID"] parameter: ["PARAM", "DeviceID.ID"],
}); });
} }
resolve({ filter, indexParameters, sort }); resolve({ filter, indexParameters, sort });
}); });
} }
function renderActions(selected): Children { function renderActions(selected: Set<string>): Children {
const buttons = []; const buttons = [];
buttons.push( buttons.push(
@@ -92,10 +94,10 @@ function renderActions(selected): Children {
for (const d of selected) { for (const d of selected) {
taskQueue.queueTask({ taskQueue.queueTask({
name: "reboot", name: "reboot",
device: d device: d,
}); });
} }
} },
}, },
"Reboot" "Reboot"
) )
@@ -111,10 +113,10 @@ function renderActions(selected): Children {
for (const d of selected) { for (const d of selected) {
taskQueue.queueTask({ taskQueue.queueTask({
name: "factoryReset", name: "factoryReset",
device: d device: d,
}); });
} }
} },
}, },
"Reset" "Reset"
) )
@@ -139,13 +141,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Deleted`); notifications.push("success", `${id}: Deleted`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", `${id}: ${err.message}`); notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}); });
} }
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
} },
}, },
"Delete" "Delete"
) )
@@ -171,13 +173,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Tags updated`); notifications.push("success", `${id}: Tags updated`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", `${id}: ${err.message}`); notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}); });
} }
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
} },
}, },
"Tag" "Tag"
) )
@@ -205,13 +207,13 @@ function renderActions(selected): Children {
notifications.push("success", `${id}: Tags updated`); notifications.push("success", `${id}: Tags updated`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", `${id}: ${err.message}`); notifications.push("error", `${id}: ${err.message}`);
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
}); });
} }
if (--counter === 0) store.fulfill(0, Date.now()); if (--counter === 0) store.fulfill(0, Date.now());
} },
}, },
"Untag" "Untag"
) )
@@ -222,7 +224,7 @@ function renderActions(selected): Children {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Devices - GenieACS"; document.title = "Devices - GenieACS";
const attributes = vnode.attrs["indexParameters"]; const attributes = vnode.attrs["indexParameters"];
@@ -273,7 +275,7 @@ export const component: ClosureComponent = (): Component => {
const devs = store.fetch("devices", filter, { const devs = store.fetch("devices", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("devices", filter); const count = store.count("devices", filter);
@@ -302,7 +304,7 @@ export const component: ClosureComponent = (): Component => {
{ {
href: `#!/devices/${encodeURIComponent( href: `#!/devices/${encodeURIComponent(
device["DeviceID.ID"].value[0] device["DeviceID.ID"].value[0]
)}` )}`,
}, },
"Show" "Show"
); );
@@ -319,8 +321,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing devices"), m("h1", "Listing devices"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
+36 -36
View File
@@ -22,7 +22,7 @@ import m, {
Child, Child,
ClosureComponent, ClosureComponent,
Component, Component,
VnodeDOM VnodeDOM,
} from "mithril"; } from "mithril";
import * as taskQueue from "./task-queue"; import * as taskQueue from "./task-queue";
import * as store from "./store"; import * as store from "./store";
@@ -50,18 +50,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
"select", "select",
{ {
value: task.parameterValues[0][1].toString(), value: task.parameterValues[0][1].toString(),
onchange: e => { onchange: (e) => {
e.redraw = false; e.redraw = false;
task.parameterValues[0][1] = input.dom.value; task.parameterValues[0][1] = input.dom.value;
}, },
onkeydown: keydown, onkeydown: keydown,
oncreate: vnode => { oncreate: (vnode) => {
(vnode.dom as HTMLSelectElement).focus(); (vnode.dom as HTMLSelectElement).focus();
} },
}, },
[ [
m("option", { value: "true" }, "true"), m("option", { value: "true" }, "true"),
m("option", { value: "false" }, "false") m("option", { value: "false" }, "false"),
] ]
); );
} else { } else {
@@ -72,18 +72,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
? "number" ? "number"
: "text", : "text",
value: task.parameterValues[0][1], value: task.parameterValues[0][1],
oninput: e => { oninput: (e) => {
e.redraw = false; e.redraw = false;
task.parameterValues[0][1] = input.dom.value; task.parameterValues[0][1] = input.dom.value;
}, },
onkeydown: keydown, onkeydown: keydown,
oncreate: vnode => { oncreate: (vnode) => {
(vnode.dom as HTMLInputElement).focus(); (vnode.dom as HTMLInputElement).focus();
(vnode.dom as HTMLInputElement).select(); (vnode.dom as HTMLInputElement).select();
// Need to prevent scrolling on focus because // Need to prevent scrolling on focus because
// we're animating height and using overflow: hidden // we're animating height and using overflow: hidden
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0; (vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
} },
}); });
} }
@@ -104,8 +104,8 @@ function renderStagingDownload(task): Children {
"2 Web Content", "2 Web Content",
"3 Vendor Configuration File", "3 Vendor Configuration File",
"4 Tone File", "4 Tone File",
"5 Ringer File" "5 Ringer File",
].map(t => ].map((t) =>
m( m(
"option", "option",
{ disabled: !t, value: t, selected: (task.fileType || "") === t }, { disabled: !t, value: t, selected: (task.fileType || "") === t },
@@ -117,14 +117,14 @@ function renderStagingDownload(task): Children {
.concat( .concat(
files.value files.value
.filter( .filter(
f => (f) =>
(!f["metadata.oui"] || f["metadata.oui"] === oui) && (!f["metadata.oui"] || f["metadata.oui"] === oui) &&
(!f["metadata.productClass"] || (!f["metadata.productClass"] ||
f["metadata.productClass"] === productClass) f["metadata.productClass"] === productClass)
) )
.map(f => f._id) .map((f) => f._id)
) )
.map(f => .map((f) =>
m( m(
"option", "option",
{ disabled: !f, value: f, selected: (task.fileName || "") === f }, { disabled: !f, value: f, selected: (task.fileName || "") === f },
@@ -137,7 +137,7 @@ function renderStagingDownload(task): Children {
m( m(
"select", "select",
{ {
onchange: e => { onchange: (e) => {
const f = e.target.value; const f = e.target.value;
task.fileName = f; task.fileName = f;
task.fileType = ""; task.fileType = "";
@@ -145,7 +145,7 @@ function renderStagingDownload(task): Children {
if (file._id === f) task.fileType = file["metadata.fileType"]; if (file._id === f) task.fileType = file["metadata.fileType"];
}, },
disabled: files.fulfilling, disabled: files.fulfilling,
style: "width: 350px" style: "width: 350px",
}, },
filesList filesList
), ),
@@ -153,12 +153,12 @@ function renderStagingDownload(task): Children {
m( m(
"select", "select",
{ {
onchange: e => { onchange: (e) => {
task.fileType = e.target.value; task.fileType = e.target.value;
} },
}, },
typesList typesList
) ),
]; ];
} }
@@ -216,7 +216,7 @@ function renderQueue(queue): Child[] {
title: "Retry this task", title: "Retry this task",
onclick: () => { onclick: () => {
taskQueue.queueTask(t); taskQueue.queueTask(t);
} },
}, },
getIcon("retry") getIcon("retry")
) )
@@ -230,7 +230,7 @@ function renderQueue(queue): Child[] {
title: "Remove this task", title: "Remove this task",
onclick: () => { onclick: () => {
taskQueue.deleteTask(t); taskQueue.deleteTask(t);
} },
}, },
getIcon("remove") getIcon("remove")
) )
@@ -324,18 +324,18 @@ function renderNotifications(notifs): Child[] {
{ {
class: n.type, class: n.type,
style: "position: absolute;opacity: 0", style: "position: absolute;opacity: 0",
oncreate: vnode => { oncreate: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "1"; (vnode.dom as HTMLDivElement).style.opacity = "1";
}, },
onbeforeremove: vnode => { onbeforeremove: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "0"; (vnode.dom as HTMLDivElement).style.opacity = "0";
return new Promise(resolve => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(); resolve();
}, 500); }, 500);
}); });
}, },
key: n.timestamp key: n.timestamp,
}, },
m("div", buttons, n.message) m("div", buttons, n.message)
) )
@@ -346,7 +346,7 @@ function renderNotifications(notifs): Child[] {
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const queue = taskQueue.getQueue(); const queue = taskQueue.getQueue();
const staging = taskQueue.getStaging(); const staging = taskQueue.getStaging();
const notifs = notifications.getNotifications(); const notifs = notifications.getNotifications();
@@ -395,7 +395,7 @@ const component: ClosureComponent = (): Component => {
disabled: !statusCount.queued, disabled: !statusCount.queued,
onclick: () => { onclick: () => {
const tasks = Array.from(taskQueue.getQueue()).filter( const tasks = Array.from(taskQueue.getQueue()).filter(
t => t["status"] === "queued" (t) => t["status"] === "queued"
); );
taskQueue taskQueue
.commit( .commit(
@@ -442,7 +442,7 @@ const component: ClosureComponent = (): Component => {
.then(() => { .then(() => {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Commit" "Commit"
), ),
@@ -451,7 +451,7 @@ const component: ClosureComponent = (): Component => {
{ {
title: "Clear tasks", title: "Clear tasks",
onclick: taskQueue.clear, onclick: taskQueue.clear,
disabled: !queueElements.length disabled: !queueElements.length,
}, },
"Clear" "Clear"
) )
@@ -487,31 +487,31 @@ const component: ClosureComponent = (): Component => {
{ {
key: "drawer", key: "drawer",
style: "opacity: 0;height: 0;", style: "opacity: 0;height: 0;",
oncreate: vnode2 => { oncreate: (vnode2) => {
vnode.state["mouseIn"] = false; vnode.state["mouseIn"] = false;
(vnode2.dom as HTMLDivElement).style.opacity = "1"; (vnode2.dom as HTMLDivElement).style.opacity = "1";
resizeDrawer(); resizeDrawer();
}, },
onmouseover: e => { onmouseover: (e) => {
vnode.state["mouseIn"] = true; vnode.state["mouseIn"] = true;
resizeDrawer(); resizeDrawer();
e.redraw = false; e.redraw = false;
}, },
onmouseleave: e => { onmouseleave: (e) => {
vnode.state["mouseIn"] = false; vnode.state["mouseIn"] = false;
resizeDrawer(); resizeDrawer();
e.redraw = false; e.redraw = false;
}, },
onupdate: resizeDrawer, onupdate: resizeDrawer,
onbeforeremove: vnode2 => { onbeforeremove: (vnode2) => {
(vnode2.dom as HTMLDivElement).onmouseover = null; (vnode2.dom as HTMLDivElement).onmouseover = null;
(vnode2.dom as HTMLDivElement).onmouseleave = null; (vnode2.dom as HTMLDivElement).onmouseleave = null;
(vnode2.dom as HTMLDivElement).style.opacity = "0"; (vnode2.dom as HTMLDivElement).style.opacity = "0";
(vnode2.dom as HTMLDivElement).style.height = "0"; (vnode2.dom as HTMLDivElement).style.height = "0";
return new Promise(resolve => { return new Promise((resolve) => {
setTimeout(resolve, 500); setTimeout(resolve, 500);
}); });
} },
}, },
statusElement, statusElement,
stagingElements.length ? stagingElements : m(".queue", queueElements) stagingElements.length ? stagingElements : m(".queue", queueElements)
@@ -527,12 +527,12 @@ const component: ClosureComponent = (): Component => {
key: "notifications", key: "notifications",
style: "position: relative;", style: "position: relative;",
onupdate: repositionNotifications, onupdate: repositionNotifications,
oncreate: repositionNotifications oncreate: repositionNotifications,
}, },
notificationElements notificationElements
) )
); );
} },
}; };
}; };
+6 -6
View File
@@ -32,7 +32,7 @@ function onError(): void {
{ {
Reload: () => { Reload: () => {
window.location.reload(); window.location.reload();
} },
} }
); );
} }
@@ -47,14 +47,14 @@ export function loadCodeMirror(): Promise<void> {
import( import(
/* webpackChunkName: "codemirror" */ "codemirror/mode/javascript/javascript" /* webpackChunkName: "codemirror" */ "codemirror/mode/javascript/javascript"
), ),
import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml") import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml"),
]; ];
Promise.all(promises) Promise.all(promises)
.then(modules => { .then((modules) => {
codeMirror = modules[0]; codeMirror = modules[0];
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
onError(); onError();
reject(err); reject(err);
}); });
@@ -66,11 +66,11 @@ export function loadYaml(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
import(/* webpackChunkName: "yaml" */ "yaml") import(/* webpackChunkName: "yaml" */ "yaml")
.then(module => { .then((module) => {
yaml = module; yaml = module;
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
onError(); onError();
reject(err); reject(err);
}); });
+2 -2
View File
@@ -22,9 +22,9 @@ import { m } from "./components";
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: function(vnode) { view: function (vnode) {
document.title = "Error! - GenieACS"; document.title = "Error! - GenieACS";
return m("p.error", vnode.attrs["error"]); return m("p.error", vnode.attrs["error"]);
} },
}; };
}; };
+18 -16
View File
@@ -41,10 +41,10 @@ const attributes = [
{ id: "message", label: "Message" }, { id: "message", label: "Message" },
{ id: "detail", label: "Detail" }, { id: "detail", label: "Detail" },
{ id: "retries", label: "Retries" }, { id: "retries", label: "Retries" },
{ id: "timestamp", label: "Timestamp" } { id: "timestamp", label: "Timestamp" },
]; ];
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize((filter) => {
const cols = {}; const cols = {};
for (const attr of attributes) { for (const attr of attributes) {
cols[attr.label] = cols[attr.label] =
@@ -53,19 +53,21 @@ const getDownloadUrl = memoize(filter => {
return `api/faults.csv?${m.buildQueryString({ return `api/faults.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(cols) columns: JSON.stringify(cols),
})}`; })}`;
}); });
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return map(query, e => { return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("faults", e[2], e[3]); return smartQuery.unpack("faults", e[2], e[3]);
return e; return e;
}); });
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("faults", 2)) { if (!window.authorizer.hasAccess("faults", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -85,7 +87,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Faults - GenieACS"; document.title = "Faults - GenieACS";
function showMore(): void { function showMore(): void {
@@ -130,7 +132,7 @@ export const component: ClosureComponent = (): Component => {
const faults = store.fetch("faults", filter, { const faults = store.fetch("faults", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("faults", filter); const count = store.count("faults", filter);
@@ -165,13 +167,13 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl; attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("faults", 3)) { if (window.authorizer.hasAccess("faults", 3)) {
attrs["actionsCallback"] = (selected): Children => { attrs["actionsCallback"] = (selected: Set<string>): Children => {
return m( return m(
"button.primary", "button.primary",
{ {
disabled: selected.size === 0, disabled: selected.size === 0,
title: "Delete selected faults", title: "Delete selected faults",
onclick: e => { onclick: (e) => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
@@ -179,22 +181,22 @@ export const component: ClosureComponent = (): Component => {
return; return;
Promise.all( Promise.all(
Array.from(selected).map(id => Array.from(selected).map((id) =>
store.deleteResource("faults", id) store.deleteResource("faults", id)
) )
) )
.then(res => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} faults deleted` `${res.length} faults deleted`
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Delete" "Delete"
); );
@@ -209,8 +211,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing faults"), m("h1", "Listing faults"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
+31 -29
View File
@@ -51,23 +51,23 @@ const attributes: {
"2 Web Content", "2 Web Content",
"3 Vendor Configuration File", "3 Vendor Configuration File",
"4 Tone File", "4 Tone File",
"5 Ringer File" "5 Ringer File",
] ],
}, },
{ id: "metadata.oui", label: "OUI" }, { id: "metadata.oui", label: "OUI" },
{ id: "metadata.productClass", label: "Product Class" }, { id: "metadata.productClass", label: "Product Class" },
{ id: "metadata.version", label: "Version" } { id: "metadata.version", label: "Version" },
]; ];
const formData = { const formData = {
resource: "files", resource: "files",
attributes: attributes attributes: attributes
.slice(1) // remove _id from new object form .slice(1) // remove _id from new object form
.concat([{ id: "file", label: "File", type: "file" }]) .concat([{ id: "file", label: "File", type: "file" }]),
}; };
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return map(query, e => { return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("files", e[2], e[3]); return smartQuery.unpack("files", e[2], e[3]);
return e; return e;
@@ -91,7 +91,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store store
.resourceExists("files", id) .resourceExists("files", id)
.then(exists => { .then((exists) => {
if (exists) { if (exists) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return void resolve({ file: "File already exists" }); return void resolve({ file: "File already exists" });
@@ -99,7 +99,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
const headers = Object.assign( const headers = Object.assign(
{ {
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
Accept: "application/octet-stream" Accept: "application/octet-stream",
}, },
object object
); );
@@ -109,8 +109,8 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
method: "PUT", method: "PUT",
headers: headers, headers: headers,
url: `api/files/${encodeURIComponent(id)}`, url: `api/files/${encodeURIComponent(id)}`,
serialize: body => body, // Identity function to prevent JSON.parse on blob data serialize: (body) => body, // Identity function to prevent JSON.parse on blob data
body: file body: file,
}) })
.then(() => { .then(() => {
notifications.push( notifications.push(
@@ -129,16 +129,18 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
}); });
} }
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize((filter) => {
const cols = {}; const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `api/files.csv?${m.buildQueryString({ return `api/files.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(cols) columns: JSON.stringify(cols),
})}`; })}`;
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("files", 2)) { if (!window.authorizer.hasAccess("files", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -152,7 +154,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Files - GenieACS"; document.title = "Files - GenieACS";
function showMore(): void { function showMore(): void {
@@ -195,7 +197,7 @@ export const component: ClosureComponent = (): Component => {
const files = store.fetch("files", filter, { const files = store.fetch("files", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("files", filter); const count = store.count("files", filter);
@@ -212,7 +214,7 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl; attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("files", 3)) { if (window.authorizer.hasAccess("files", 3)) {
attrs["actionsCallback"] = (selected): Children => { attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [ return [
m( m(
"button.primary", "button.primary",
@@ -225,9 +227,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign( Object.assign(
{ {
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object) putActionHandler(action, object)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -239,19 +241,19 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"New" "New"
), ),
@@ -260,7 +262,7 @@ export const component: ClosureComponent = (): Component => {
{ {
title: "Delete selected files", title: "Delete selected files",
disabled: !selected.size, disabled: !selected.size,
onclick: e => { onclick: (e) => {
if ( if (
!confirm(`Deleting ${selected.size} files. Are you sure?`) !confirm(`Deleting ${selected.size} files. Are you sure?`)
) )
@@ -269,25 +271,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
Promise.all( Promise.all(
Array.from(selected).map(id => Array.from(selected).map((id) =>
store.deleteResource("files", id) store.deleteResource("files", id)
) )
) )
.then(res => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} files deleted` `${res.length} files deleted`
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Delete" "Delete"
) ),
]; ];
}; };
} }
@@ -300,8 +302,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing files"), m("h1", "Listing files"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
+15 -13
View File
@@ -25,16 +25,16 @@ import * as smartQuery from "./smart-query";
import { filterToMongoQuery } from "../lib/mongodb-functions"; import { filterToMongoQuery } from "../lib/mongodb-functions";
import { Expression } from "../lib/types"; import { Expression } from "../lib/types";
const getAutocomplete = memoize(resource => { const getAutocomplete = memoize((resource) => {
const labels = smartQuery.getLabels(resource); const labels = smartQuery.getLabels(resource);
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => { const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
txt = txt.toLowerCase(); txt = txt.toLowerCase();
cb( cb(
labels labels
.filter(s => s.toLowerCase().includes(txt)) .filter((s) => s.toLowerCase().includes(txt))
.map(s => ({ .map((s) => ({
value: `${s}: `, value: `${s}: `,
tip: smartQuery.getTip(resource, s) tip: smartQuery.getTip(resource, s),
})) }))
); );
}); });
@@ -53,7 +53,7 @@ function parseFilter(resource, f): Expression {
// Throw exception if invalid Mongo query // Throw exception if invalid Mongo query
filterToMongoQuery( filterToMongoQuery(
map(exp, e => { map(exp, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack(resource, e[2], e[3]); return smartQuery.unpack(resource, e[2], e[3]);
return e; return e;
@@ -69,7 +69,7 @@ function stringifyFilter(f: Expression): string {
return stringify(f); return stringify(f);
} }
const splitFilter = memoize(filter => { const splitFilter = memoize((filter) => {
if (!filter) return [""]; if (!filter) return [""];
const list = []; const list = [];
const f = parse(filter); const f = parse(filter);
@@ -83,7 +83,7 @@ const splitFilter = memoize(filter => {
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
if ( if (
!vnode.state["filterList"] || !vnode.state["filterList"] ||
vnode.attrs["filter"] !== vnode.state["filter"] vnode.attrs["filter"] !== vnode.state["filter"]
@@ -95,7 +95,7 @@ const component: ClosureComponent = (): Component => {
function onChange(): void { function onChange(): void {
vnode.state["filterInvalid"] = 0; vnode.state["filterInvalid"] = 0;
vnode.state["filterList"] = vnode.state["filterList"].filter(f => f); vnode.state["filterList"] = vnode.state["filterList"].filter((f) => f);
let filter = vnode.state["filterList"].map((f, idx) => { let filter = vnode.state["filterList"].map((f, idx) => {
try { try {
return parseFilter(vnode.attrs["resource"], f); return parseFilter(vnode.attrs["resource"], f);
@@ -128,19 +128,21 @@ const component: ClosureComponent = (): Component => {
(vnode.state["filterInvalid"] >> idx) & 1 ? "error" : "" (vnode.state["filterInvalid"] >> idx) & 1 ? "error" : ""
}`, }`,
value: fltr, value: fltr,
onchange: e => { onchange: (e) => {
vnode.state["filterList"] = vnode.state["filterList"].slice(); vnode.state["filterList"] = vnode.state["filterList"].slice();
vnode.state["filterList"][idx] = e.target.value.trim(); vnode.state["filterList"][idx] = e.target.value.trim();
onChange(); onChange();
}, },
oncreate: vn => { oncreate: (vn) => {
getAutocomplete(vnode.attrs["resource"]).attach(vn.dom); getAutocomplete(vnode.attrs["resource"]).attach(
} vn.dom as HTMLInputElement
);
},
}); });
}) })
) )
); );
} },
}; };
}; };
+12 -12
View File
@@ -57,14 +57,14 @@ function renderTable(
const selectAll = m("input", { const selectAll = m("input", {
type: "checkbox", type: "checkbox",
checked: records.length && selected.size === records.length, checked: records.length && selected.size === records.length,
onchange: e => { onchange: (e) => {
for (const record of records) { for (const record of records) {
const id = record["_id"] || record["DeviceID.ID"].value[0]; const id = record["_id"] || record["DeviceID.ID"].value[0];
if (e.target.checked) selected.add(id); if (e.target.checked) selected.add(id);
else selected.delete(id); else selected.delete(id);
} }
}, },
disabled: !total disabled: !total,
}); });
labels.push(m("th", selectAll)); labels.push(m("th", selectAll));
} }
@@ -89,7 +89,7 @@ function renderTable(
onclick: () => { onclick: () => {
if (sortAttributes[i] > 0) direction *= -1; if (sortAttributes[i] > 0) direction *= -1;
return onSortChange({ [i]: direction }); return onSortChange({ [i]: direction });
} },
}, },
symbol symbol
); );
@@ -106,14 +106,14 @@ function renderTable(
const checkbox = m("input", { const checkbox = m("input", {
type: "checkbox", type: "checkbox",
checked: selected.has(id), checked: selected.has(id),
onchange: e => { onchange: (e) => {
if (e.target.checked) selected.add(id); if (e.target.checked) selected.add(id);
else selected.delete(id); else selected.delete(id);
}, },
onclick: e => { onclick: (e) => {
e.stopPropagation(); e.stopPropagation();
e.redraw = false; e.redraw = false;
} },
}); });
tds.push(m("td", checkbox)); tds.push(m("td", checkbox));
} }
@@ -152,14 +152,14 @@ function renderTable(
m( m(
"tr", "tr",
{ {
onclick: e => { onclick: (e) => {
if (["INPUT", "BUTTON", "A"].includes(e.target.nodeName)) { if (["INPUT", "BUTTON", "A"].includes(e.target.nodeName)) {
e.redraw = false; e.redraw = false;
return; return;
} }
if (!selected.delete(id)) selected.add(id); if (!selected.delete(id)) selected.add(id);
} },
}, },
tds tds
) )
@@ -180,7 +180,7 @@ function renderTable(
{ {
title: "Show more records", title: "Show more records",
onclick: showMoreCallback, onclick: showMoreCallback,
disabled: !data.length || records.length >= total disabled: !data.length || records.length >= total,
}, },
"More" "More"
) )
@@ -203,7 +203,7 @@ function renderTable(
m("thead", m("tr", labels)), m("thead", m("tr", labels)),
m("tbody", rows), m("tbody", rows),
tfoot tfoot
) ),
]; ];
if (buttons.length) children.push(m("div.actions-bar", buttons)); if (buttons.length) children.push(m("div.actions-bar", buttons));
@@ -214,7 +214,7 @@ const component: ClosureComponent = (): Component => {
let selected = new Set<string>(); let selected = new Set<string>();
return { return {
view: vnode => { view: (vnode) => {
const attributes = vnode.attrs["attributes"]; const attributes = vnode.attrs["attributes"];
const data = vnode.attrs["data"]; const data = vnode.attrs["data"];
const valueCallback = vnode.attrs["valueCallback"]; const valueCallback = vnode.attrs["valueCallback"];
@@ -246,7 +246,7 @@ const component: ClosureComponent = (): Component => {
actionsCallback, actionsCallback,
recordActionsCallback recordActionsCallback
); );
} },
}; };
}; };
+6 -6
View File
@@ -31,12 +31,12 @@ const adminPages = [
"files", "files",
"config", "config",
"users", "users",
"permissions" "permissions",
]; ];
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
let sideMenu, group; let sideMenu, group;
if (adminPages.includes(vnode.attrs["page"])) { if (adminPages.includes(vnode.attrs["page"])) {
@@ -54,18 +54,18 @@ const component: ClosureComponent = (): Component => {
m("img.logo", { src: "logo.svg" }), m("img.logo", { src: "logo.svg" }),
m(userMenu), m(userMenu),
m(menu, attrs), m(menu, attrs),
m(drawerComponent) m(drawerComponent),
]), ]),
m( m(
"#content-wrapper", "#content-wrapper",
sideMenu, sideMenu,
m("#content", { class: `page-${vnode.attrs["page"]}` }, [ m("#content", { class: `page-${vnode.attrs["page"]}` }, [
vnode.children vnode.children,
]) ])
), ),
overlay.render() overlay.render(),
]; ];
} },
}; };
}; };
+16 -14
View File
@@ -24,13 +24,15 @@ import * as notifications from "./notifications";
import * as overlay from "./overlay"; import * as overlay from "./overlay";
import changePasswordComponent from "./change-password-component"; import changePasswordComponent from "./change-password-component";
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
return Promise.resolve(args); return Promise.resolve(args);
} }
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
if (window.username) m.route.set(vnode.attrs["continue"] || "/"); if (window.username) m.route.set(vnode.attrs["continue"] || "/");
document.title = "Login - GenieACS"; document.title = "Login - GenieACS";
@@ -45,12 +47,12 @@ export const component: ClosureComponent = (): Component => {
name: "username", name: "username",
type: "text", type: "text",
value: vnode.state["username"], value: vnode.state["username"],
oncreate: vnode2 => { oncreate: (vnode2) => {
(vnode2.dom as HTMLInputElement).focus(); (vnode2.dom as HTMLInputElement).focus();
}, },
oninput: e => { oninput: (e) => {
vnode.state["username"] = e.target.value; vnode.state["username"] = e.target.value;
} },
}) })
), ),
m( m(
@@ -60,9 +62,9 @@ export const component: ClosureComponent = (): Component => {
name: "password", name: "password",
type: "password", type: "password",
value: vnode.state["password"], value: vnode.state["password"],
oninput: e => { oninput: (e) => {
vnode.state["password"] = e.target.value; vnode.state["password"] = e.target.value;
} },
}) })
), ),
m( m(
@@ -71,19 +73,19 @@ export const component: ClosureComponent = (): Component => {
"button.primary", "button.primary",
{ {
type: "submit", type: "submit",
onclick: e => { onclick: (e) => {
e.target.disabled = true; e.target.disabled = true;
store store
.logIn(vnode.state["username"], vnode.state["password"]) .logIn(vnode.state["username"], vnode.state["password"])
.then(() => { .then(() => {
location.reload(); location.reload();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
e.target.disabled = false; e.target.disabled = false;
}); });
return false; return false;
} },
}, },
"Login" "Login"
) )
@@ -98,16 +100,16 @@ export const component: ClosureComponent = (): Component => {
onPasswordChange: () => { onPasswordChange: () => {
overlay.close(cb); overlay.close(cb);
m.redraw(); m.redraw();
} },
}; };
return m(changePasswordComponent, attrs); return m(changePasswordComponent, attrs);
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"Change password" "Change password"
) ),
]; ];
} },
}; };
}; };
+8 -8
View File
@@ -22,23 +22,23 @@ import * as overlay from "./overlay";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const text = vnode.attrs["text"]; const text = vnode.attrs["text"];
const element = vnode.attrs["element"] || "span"; const element = vnode.attrs["element"] || "span";
function overflowed(_vnode): void { function overflowed(_vnode): void {
_vnode.dom.classList.add("long-text-overflowed"); _vnode.dom.classList.add("long-text-overflowed");
_vnode.dom.onclick = e => { _vnode.dom.onclick = (e) => {
overlay.open(() => { overlay.open(() => {
return m("textarea.long-text", { return m("textarea.long-text", {
value: text, value: text,
cols: 80, cols: 80,
rows: 24, rows: 24,
readonly: "", readonly: "",
oncreate: vnode2 => { oncreate: (vnode2) => {
(vnode2.dom as HTMLTextAreaElement).focus(); (vnode2.dom as HTMLTextAreaElement).focus();
(vnode2.dom as HTMLTextAreaElement).select(); (vnode2.dom as HTMLTextAreaElement).select();
} },
}); });
}); });
// prevent index page selection // prevent index page selection
@@ -50,11 +50,11 @@ const component: ClosureComponent = (): Component => {
return m( return m(
element, element,
{ {
oncreate: vnode2 => { oncreate: (vnode2) => {
if (vnode2.dom.clientWidth !== vnode2.dom.scrollWidth) if (vnode2.dom.clientWidth !== vnode2.dom.scrollWidth)
overflowed(vnode2); overflowed(vnode2);
}, },
onupdate: vnode2 => { onupdate: (vnode2) => {
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) { if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
(vnode2.dom as HTMLElement).classList.remove( (vnode2.dom as HTMLElement).classList.remove(
"long-text-overflowed" "long-text-overflowed"
@@ -65,11 +65,11 @@ const component: ClosureComponent = (): Component => {
} }
}, },
class: "long-text", class: "long-text",
title: text title: text,
}, },
text text
); );
} },
}; };
}; };
+3 -3
View File
@@ -21,7 +21,7 @@ import m, { ClosureComponent, Component } from "mithril";
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
const active = { [vnode.attrs["page"]]: "active" }; const active = { [vnode.attrs["page"]]: "active" };
const tabs = []; const tabs = [];
@@ -59,7 +59,7 @@ const component: ClosureComponent = (): Component => {
"presets", "presets",
"provisions", "provisions",
"virtualParameters", "virtualParameters",
"files" "files",
]; ];
for (const page of adminPages) { for (const page of adminPages) {
if (window.authorizer.hasAccess(page, 2)) { if (window.authorizer.hasAccess(page, 2)) {
@@ -75,7 +75,7 @@ const component: ClosureComponent = (): Component => {
} }
return m("nav", m("ul", tabs)); return m("nav", m("ul", tabs));
} },
}; };
}; };
+6 -2
View File
@@ -28,12 +28,16 @@ interface Notification {
const notifications = new Set<Notification>(); const notifications = new Set<Notification>();
export function push(type, message, actions?): Notification { export function push(
type: string,
message: string,
actions?: { [label: string]: () => void }
): Notification {
const n: Notification = { const n: Notification = {
type: type, type: type,
message: message, message: message,
timestamp: Date.now(), timestamp: Date.now(),
actions: actions actions: actions,
}; };
notifications.add(n); notifications.add(n);
m.redraw(); m.redraw();
+7 -7
View File
@@ -46,25 +46,25 @@ export function render(): Children {
close(overlayCallback); close(overlayCallback);
}, },
style: "opacity: 0", style: "opacity: 0",
oncreate: vnode => { oncreate: (vnode) => {
(vnode.dom as HTMLDivElement).focus(); (vnode.dom as HTMLDivElement).focus();
(vnode.dom as HTMLDivElement).style.opacity = "1"; (vnode.dom as HTMLDivElement).style.opacity = "1";
}, },
onbeforeremove: vnode => { onbeforeremove: (vnode) => {
(vnode.dom as HTMLDivElement).style.opacity = "0"; (vnode.dom as HTMLDivElement).style.opacity = "0";
return new Promise(resolve => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(); resolve();
}, 500); }, 500);
}); });
} },
}, },
m( m(
".overlay", ".overlay",
{ {
onclick: e => { onclick: (e) => {
e.stopPropagation(); e.stopPropagation();
} },
}, },
overlayCallback() overlayCallback()
) )
@@ -74,7 +74,7 @@ export function render(): Children {
return null; return null;
} }
document.addEventListener("keydown", e => { document.addEventListener("keydown", (e) => {
if (overlayCallback && e.keyCode === 27 && close(overlayCallback)) m.redraw(); if (overlayCallback && e.keyCode === 27 && close(overlayCallback)) m.redraw();
}); });
+4 -4
View File
@@ -30,7 +30,7 @@ for (const group of Object.values(GROUPS)) {
CHARTS[chartName] = config.ui.overview.charts[chartName]; CHARTS[chartName] = config.ui.overview.charts[chartName];
} }
function queryCharts(charts): {} { function queryCharts(charts: Record<string, unknown>): Record<string, unknown> {
charts = Object.assign({}, charts); charts = Object.assign({}, charts);
for (let [chartName, chart] of Object.entries(charts)) { for (let [chartName, chart] of Object.entries(charts)) {
charts[chartName] = chart = Object.assign({}, chart); charts[chartName] = chart = Object.assign({}, chart);
@@ -44,7 +44,7 @@ function queryCharts(charts): {} {
return charts; return charts;
} }
export function init(): Promise<{}> { export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 1)) { if (!window.authorizer.hasAccess("devices", 1)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -56,7 +56,7 @@ export function init(): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Overview - GenieACS"; document.title = "Overview - GenieACS";
const children = []; const children = [];
for (const group of Object.values(GROUPS)) { for (const group of Object.values(GROUPS)) {
@@ -79,6 +79,6 @@ export const component: ClosureComponent = (): Component => {
} }
return children; return children;
} },
}; };
}; };
+36 -34
View File
@@ -51,21 +51,21 @@ const attributes = [
"users", "users",
"presets", "presets",
"provisions", "provisions",
"virtualParameters" "virtualParameters",
] ],
}, },
{ id: "filter", label: "Filter", type: "textarea" }, { id: "filter", label: "Filter", type: "textarea" },
{ {
id: "access", id: "access",
label: "Access", label: "Access",
type: "combo", type: "combo",
options: ["1: count", "2: read", "3: write"] options: ["1: count", "2: read", "3: write"],
}, },
{ id: "validate", label: "Validate", type: "textarea" } { id: "validate", label: "Validate", type: "textarea" },
]; ];
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return map(query, e => { return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("permissions", e[2], e[3]); return smartQuery.unpack("permissions", e[2], e[3]);
return e; return e;
@@ -96,7 +96,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
object.filter = stringify(memoizedParse(object.filter)); object.filter = stringify(memoizedParse(object.filter));
} catch (err) { } catch (err) {
return void resolve({ return void resolve({
filter: "Filter must be valid expression" filter: "Filter must be valid expression",
}); });
} }
} }
@@ -106,7 +106,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
object.validate = stringify(memoizedParse(object.validate)); object.validate = stringify(memoizedParse(object.validate));
} catch (err) { } catch (err) {
return void resolve({ return void resolve({
validate: "Validate must be valid expression" validate: "Validate must be valid expression",
}); });
} }
} }
@@ -115,7 +115,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store store
.resourceExists("permissions", id) .resourceExists("permissions", id)
.then(exists => { .then((exists) => {
if (exists) { if (exists) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return void resolve({ _id: "Permission already exists" }); return void resolve({ _id: "Permission already exists" });
@@ -139,7 +139,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
reject(err); reject(err);
}); });
@@ -151,19 +151,21 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
const formData = { const formData = {
resource: "permissions", resource: "permissions",
attributes: attributes attributes: attributes,
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize((filter) => {
const cols = {}; const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `api/permissions.csv?${m.buildQueryString({ return `api/permissions.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(cols) columns: JSON.stringify(cols),
})}`; })}`;
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("permissions", 2)) { if (!window.authorizer.hasAccess("permissions", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -176,7 +178,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Permissions - GenieACS"; document.title = "Permissions - GenieACS";
function showMore(): void { function showMore(): void {
@@ -222,7 +224,7 @@ export const component: ClosureComponent = (): Component => {
const permissions = store.fetch("permissions", filter, { const permissions = store.fetch("permissions", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("permissions", filter); const count = store.count("permissions", filter);
@@ -252,7 +254,7 @@ export const component: ClosureComponent = (): Component => {
attrs["downloadUrl"] = downloadUrl; attrs["downloadUrl"] = downloadUrl;
if (window.authorizer.hasAccess("permissions", 3)) { if (window.authorizer.hasAccess("permissions", 3)) {
attrs["recordActionsCallback"] = permission => { attrs["recordActionsCallback"] = (permission) => {
return [ return [
m( m(
"button", "button",
@@ -266,17 +268,17 @@ export const component: ClosureComponent = (): Component => {
) )
return; return;
putActionHandler("delete", permission).catch(err => { putActionHandler("delete", permission).catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
}); });
} },
}, },
getIcon("remove") getIcon("remove")
) ),
]; ];
}; };
attrs["actionsCallback"] = (selected): Children => { attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [ return [
m( m(
"button.primary", "button.primary",
@@ -289,9 +291,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign( Object.assign(
{ {
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object) putActionHandler(action, object)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -303,19 +305,19 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"New" "New"
), ),
@@ -324,7 +326,7 @@ export const component: ClosureComponent = (): Component => {
{ {
title: "Delete selected permissions", title: "Delete selected permissions",
disabled: !selected.size, disabled: !selected.size,
onclick: e => { onclick: (e) => {
if ( if (
!confirm( !confirm(
`Deleting ${selected.size} permissions. Are you sure?` `Deleting ${selected.size} permissions. Are you sure?`
@@ -335,25 +337,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
Promise.all( Promise.all(
Array.from(selected).map(id => Array.from(selected).map((id) =>
store.deleteResource("permissions", id) store.deleteResource("permissions", id)
) )
) )
.then(res => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} permissions deleted` `${res.length} permissions deleted`
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Delete" "Delete"
) ),
]; ];
}; };
} }
@@ -366,8 +368,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing permissions"), m("h1", "Listing permissions"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
+14 -14
View File
@@ -43,19 +43,19 @@ function drawChart(chartData): Children {
legend.push( legend.push(
m(".legend-line", [ m(".legend-line", [
m("span.color", { m("span.color", {
style: `background-color: ${slice["color"]} !important;` style: `background-color: ${slice["color"]} !important;`,
}), }),
`${slice["label"]}: `, `${slice["label"]}: `,
m( m(
"a", "a",
{ {
href: `#!/devices/?${m.buildQueryString({ href: `#!/devices/?${m.buildQueryString({
filter: memoizedStringify(slice["filter"]) filter: memoizedStringify(slice["filter"]),
})}` })}`,
}, },
slice["count"]["value"] || 0 slice["count"]["value"] || 0
), ),
` (${(percent * 100).toFixed(2)}%)` ` (${(percent * 100).toFixed(2)}%)`,
]) ])
); );
@@ -76,7 +76,7 @@ function drawChart(chartData): Children {
paths.push( paths.push(
m("path", { m("path", {
d: sketch, d: sketch,
fill: slice["color"] fill: slice["color"],
}) })
); );
@@ -90,13 +90,13 @@ function drawChart(chartData): Children {
"a", "a",
{ {
"xlink:href": `#!/devices/?${m.buildQueryString({ "xlink:href": `#!/devices/?${m.buildQueryString({
filter: memoizedStringify(slice["filter"]) filter: memoizedStringify(slice["filter"]),
})}` })}`,
}, },
[ [
m("path", { m("path", {
d: sketch, d: sketch,
"fill-opacity": 0 "fill-opacity": 0,
}), }),
m( m(
"text", "text",
@@ -104,10 +104,10 @@ function drawChart(chartData): Children {
x: percentageX, x: percentageX,
y: percentageY, y: percentageY,
"dominant-baseline": "middle", "dominant-baseline": "middle",
"text-anchor": "middle" "text-anchor": "middle",
}, },
`${(percent * 100).toFixed(2)}%` `${(percent * 100).toFixed(2)}%`
) ),
] ]
) )
); );
@@ -125,19 +125,19 @@ function drawChart(chartData): Children {
width: "204px", width: "204px",
height: "204px", height: "204px",
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink" "xmlns:xlink": "http://www.w3.org/1999/xlink",
}, },
paths.concat(links) paths.concat(links)
), ),
m(".legend", legend) m(".legend", legend),
]); ]);
} }
const component: ClosureComponent = (): Component => { const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
return drawChart(vnode.attrs["chart"]); return drawChart(vnode.attrs["chart"]);
} },
}; };
}; };
+42 -40
View File
@@ -17,7 +17,7 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ClosureComponent, Component, Children } from "mithril"; import { ClosureComponent, Component, Children, Vnode } from "mithril";
import { m } from "./components"; import { m } from "./components";
import config from "./config"; import config from "./config";
import filterComponent from "./filter-component"; import filterComponent from "./filter-component";
@@ -43,11 +43,11 @@ const attributes = [
{ id: "events", label: "Events" }, { id: "events", label: "Events" },
{ id: "precondition", label: "Precondition", type: "textarea" }, { id: "precondition", label: "Precondition", type: "textarea" },
{ id: "provision", label: "Provision", type: "combo" }, { id: "provision", label: "Provision", type: "combo" },
{ id: "provisionArgs", label: "Arguments" } { id: "provisionArgs", label: "Arguments" },
]; ];
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return map(query, e => { return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("presets", e[2], e[3]); return smartQuery.unpack("presets", e[2], e[3]);
return e; return e;
@@ -77,14 +77,14 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
object.precondition = stringify(memoizedParse(object.precondition)); object.precondition = stringify(memoizedParse(object.precondition));
} catch (err) { } catch (err) {
return void resolve({ return void resolve({
precondition: "Precondition must be valid expression" precondition: "Precondition must be valid expression",
}); });
} }
} }
store store
.resourceExists("presets", id) .resourceExists("presets", id)
.then(exists => { .then((exists) => {
if (exists && isNew) { if (exists && isNew) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return void resolve({ _id: "Preset already exists" }); return void resolve({ _id: "Preset already exists" });
@@ -116,7 +116,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
reject(err); reject(err);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
@@ -128,19 +128,21 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const formData = { const formData = {
resource: "presets", resource: "presets",
attributes: attributes attributes: attributes,
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize((filter) => {
const cols = {}; const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `api/presets.csv?${m.buildQueryString({ return `api/presets.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(cols) columns: JSON.stringify(cols),
})}`; })}`;
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("presets", 2)) { if (!window.authorizer.hasAccess("presets", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -154,7 +156,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Presets - GenieACS"; document.title = "Presets - GenieACS";
function showMore(): void { function showMore(): void {
@@ -207,7 +209,7 @@ export const component: ClosureComponent = (): Component => {
const presets = store.fetch("presets", filter, { const presets = store.fetch("presets", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("presets", filter); const count = store.count("presets", filter);
@@ -220,7 +222,7 @@ export const component: ClosureComponent = (): Component => {
"reboot", "reboot",
"reset", "reset",
"download", "download",
"instances" "instances",
]); ]);
const provisions = store.fetch("provisions", true); const provisions = store.fetch("provisions", true);
@@ -231,19 +233,19 @@ export const component: ClosureComponent = (): Component => {
} }
} }
const provisionAttr = attributes.find(attr => { const provisionAttr = attributes.find((attr) => {
return attr.id === "provision"; return attr.id === "provision";
}); });
provisionAttr["options"] = Array.from(provisionIds); provisionAttr["options"] = Array.from(provisionIds);
const downloadUrl = getDownloadUrl(filter); const downloadUrl = getDownloadUrl(filter);
const valueCallback = (attr, preset): {} => { const valueCallback = (attr, preset): Vnode => {
if (attr.id === "precondition") { if (attr.id === "precondition") {
let devicesUrl = "#!/devices"; let devicesUrl = "#!/devices";
if (preset["precondition"].length) { if (preset["precondition"].length) {
devicesUrl += `?${m.buildQueryString({ devicesUrl += `?${m.buildQueryString({
filter: preset["precondition"] filter: preset["precondition"],
})}`; })}`;
} }
@@ -260,8 +262,8 @@ export const component: ClosureComponent = (): Component => {
"a", "a",
{ {
href: `#!/admin/provisions?${m.buildQueryString({ href: `#!/admin/provisions?${m.buildQueryString({
filter: `Q("ID", "${preset["provision"]}")` filter: `Q("ID", "${preset["provision"]}")`,
})}` })}`,
}, },
preset["provision"] preset["provision"]
); );
@@ -279,7 +281,7 @@ export const component: ClosureComponent = (): Component => {
attrs["onSortChange"] = onSortChange; attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl; attrs["downloadUrl"] = downloadUrl;
attrs["valueCallback"] = valueCallback; attrs["valueCallback"] = valueCallback;
attrs["recordActionsCallback"] = preset => { attrs["recordActionsCallback"] = (preset) => {
return [ return [
m( m(
"a", "a",
@@ -300,9 +302,9 @@ export const component: ClosureComponent = (): Component => {
{ {
base: preset, base: preset,
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, false) putActionHandler(action, object, false)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -314,27 +316,27 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"Show" "Show"
) ),
]; ];
}; };
if (window.authorizer.hasAccess("presets", 3)) { if (window.authorizer.hasAccess("presets", 3)) {
attrs["actionsCallback"] = (selected): Children => { attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [ return [
m( m(
"button.primary", "button.primary",
@@ -347,9 +349,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign( Object.assign(
{ {
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, true) putActionHandler(action, object, true)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -361,19 +363,19 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"New" "New"
), ),
@@ -382,7 +384,7 @@ export const component: ClosureComponent = (): Component => {
{ {
title: "Delete selected presets", title: "Delete selected presets",
disabled: !selected.size, disabled: !selected.size,
onclick: e => { onclick: (e) => {
if ( if (
!confirm(`Deleting ${selected.size} presets. Are you sure?`) !confirm(`Deleting ${selected.size} presets. Are you sure?`)
) )
@@ -391,25 +393,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
Promise.all( Promise.all(
Array.from(selected).map(id => Array.from(selected).map((id) =>
store.deleteResource("presets", id) store.deleteResource("presets", id)
) )
) )
.then(res => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} presets deleted` `${res.length} presets deleted`
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Delete" "Delete"
) ),
]; ];
}; };
} }
@@ -422,8 +424,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing presets"), m("h1", "Listing presets"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
+34 -32
View File
@@ -38,11 +38,11 @@ const memoizedJsonParse = memoize(JSON.parse);
const attributes = [ const attributes = [
{ id: "_id", label: "Name" }, { id: "_id", label: "Name" },
{ id: "script", label: "Script", type: "code" } { id: "script", label: "Script", type: "code" },
]; ];
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize((query) => {
return map(query, e => { return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("provisions", e[2], e[3]); return smartQuery.unpack("provisions", e[2], e[3]);
return e; return e;
@@ -64,7 +64,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store store
.resourceExists("provisions", id) .resourceExists("provisions", id)
.then(exists => { .then((exists) => {
if (exists && isNew) { if (exists && isNew) {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
return void resolve({ _id: "Provision already exists" }); return void resolve({ _id: "Provision already exists" });
@@ -96,7 +96,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
reject(err); reject(err);
}); });
@@ -108,19 +108,21 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const formData = { const formData = {
resource: "provisions", resource: "provisions",
attributes: attributes attributes: attributes,
}; };
const getDownloadUrl = memoize(filter => { const getDownloadUrl = memoize((filter) => {
const cols = {}; const cols = {};
for (const attr of attributes) cols[attr.label] = attr.id; for (const attr of attributes) cols[attr.label] = attr.id;
return `api/provisions.csv?${m.buildQueryString({ return `api/provisions.csv?${m.buildQueryString({
filter: stringify(filter), filter: stringify(filter),
columns: JSON.stringify(cols) columns: JSON.stringify(cols),
})}`; })}`;
}); });
export function init(args): Promise<{}> { export function init(
args: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("provisions", 2)) { if (!window.authorizer.hasAccess("provisions", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page")
@@ -141,7 +143,7 @@ export function init(args): Promise<{}> {
export const component: ClosureComponent = (): Component => { export const component: ClosureComponent = (): Component => {
return { return {
view: vnode => { view: (vnode) => {
document.title = "Provisions - GenieACS"; document.title = "Provisions - GenieACS";
function showMore(): void { function showMore(): void {
@@ -184,7 +186,7 @@ export const component: ClosureComponent = (): Component => {
const provisions = store.fetch("provisions", filter, { const provisions = store.fetch("provisions", filter, {
limit: vnode.state["showCount"] || PAGE_SIZE, limit: vnode.state["showCount"] || PAGE_SIZE,
sort: sort sort: sort,
}); });
const count = store.count("provisions", filter); const count = store.count("provisions", filter);
@@ -199,7 +201,7 @@ export const component: ClosureComponent = (): Component => {
attrs["sortAttributes"] = sortAttributes; attrs["sortAttributes"] = sortAttributes;
attrs["onSortChange"] = onSortChange; attrs["onSortChange"] = onSortChange;
attrs["downloadUrl"] = downloadUrl; attrs["downloadUrl"] = downloadUrl;
attrs["recordActionsCallback"] = provision => { attrs["recordActionsCallback"] = (provision) => {
return [ return [
m( m(
"a", "a",
@@ -212,9 +214,9 @@ export const component: ClosureComponent = (): Component => {
{ {
base: provision, base: provision,
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, false) putActionHandler(action, object, false)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -226,27 +228,27 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"Show" "Show"
) ),
]; ];
}; };
if (window.authorizer.hasAccess("provisions", 3)) { if (window.authorizer.hasAccess("provisions", 3)) {
attrs["actionsCallback"] = (selected): Children => { attrs["actionsCallback"] = (selected: Set<string>): Children => {
return [ return [
m( m(
"button.primary", "button.primary",
@@ -259,9 +261,9 @@ export const component: ClosureComponent = (): Component => {
Object.assign( Object.assign(
{ {
actionHandler: (action, object) => { actionHandler: (action, object) => {
return new Promise(resolve => { return new Promise((resolve) => {
putActionHandler(action, object, true) putActionHandler(action, object, true)
.then(errors => { .then((errors) => {
const errorList = errors const errorList = errors
? Object.values(errors) ? Object.values(errors)
: []; : [];
@@ -273,19 +275,19 @@ export const component: ClosureComponent = (): Component => {
} }
resolve(); resolve();
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
resolve(); resolve();
}); });
}); });
} },
}, },
formData formData
) )
); );
}; };
overlay.open(cb); overlay.open(cb);
} },
}, },
"New" "New"
), ),
@@ -294,7 +296,7 @@ export const component: ClosureComponent = (): Component => {
{ {
title: "Delete selected provisions", title: "Delete selected provisions",
disabled: !selected.size, disabled: !selected.size,
onclick: e => { onclick: (e) => {
if ( if (
!confirm( !confirm(
`Deleting ${selected.size} provisions. Are you sure?` `Deleting ${selected.size} provisions. Are you sure?`
@@ -305,25 +307,25 @@ export const component: ClosureComponent = (): Component => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
Promise.all( Promise.all(
Array.from(selected).map(id => Array.from(selected).map((id) =>
store.deleteResource("provisions", id) store.deleteResource("provisions", id)
) )
) )
.then(res => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} provisions deleted` `${res.length} provisions deleted`
); );
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}) })
.catch(err => { .catch((err) => {
notifications.push("error", err.message); notifications.push("error", err.message);
store.fulfill(0, Date.now()); store.fulfill(0, Date.now());
}); });
} },
}, },
"Delete" "Delete"
) ),
]; ];
}; };
} }
@@ -336,8 +338,8 @@ export const component: ClosureComponent = (): Component => {
return [ return [
m("h1", "Listing provisions"), m("h1", "Listing provisions"),
m(filterComponent, filterAttrs), m(filterComponent, filterAttrs),
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
]; ];
} },
}; };
}; };
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more