TypeScript!
This commit is contained in:
1 parent
ee8c43747e
commit
5d3c81cb23
105 files changed
+12054
-1901
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
!/bin/.*.ts
|
||||||
|
!/tools/.*.ts
|
||||||
+7
-4
@@ -3,11 +3,12 @@
|
|||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 8,
|
"project": "./tsconfig.json"
|
||||||
"sourceType": "module"
|
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "prettier/@typescript-eslint"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-shadow": ["error", { "allow": ["err"] }],
|
"no-shadow": ["error", { "allow": ["err"] }],
|
||||||
"handle-callback-err": "error",
|
"handle-callback-err": "error",
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
"consistent-return": ["error", {"treatUndefinedAsUnspecified": true}],
|
"consistent-return": ["error", {"treatUndefinedAsUnspecified": true}],
|
||||||
"no-buffer-constructor": "error",
|
"no-buffer-constructor": "error",
|
||||||
"prefer-const": ["error", {"destructuring": "all"}],
|
"prefer-const": ["error", {"destructuring": "all"}],
|
||||||
"eqeqeq": ["error", "always", {"null": "ignore"}]
|
"eqeqeq": ["error", "always", {"null": "ignore"}],
|
||||||
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
|
"@typescript-eslint/no-use-before-define": ["error", { "functions": false }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,3 +15,5 @@ config/fs.key
|
|||||||
config/fs.crt
|
config/fs.crt
|
||||||
config/fs.ca-bundle
|
config/fs.ca-bundle
|
||||||
config/ext
|
config/ext
|
||||||
|
bin/.*.ts
|
||||||
|
tools/.*.ts
|
||||||
@@ -17,4 +17,10 @@ config/ext
|
|||||||
debug/*
|
debug/*
|
||||||
!debug/.gitkeep
|
!debug/.gitkeep
|
||||||
|
|
||||||
|
dist
|
||||||
.eslintrc.json
|
.eslintrc.json
|
||||||
|
.eslintignore
|
||||||
|
ava.config.js
|
||||||
|
tsconfig.json
|
||||||
|
bin/.*
|
||||||
|
tools/.*
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
"compileEnhancements": false,
|
||||||
|
"extensions": ["ts"],
|
||||||
|
"require": ["esm", "ts-node/register/transpile-only"]
|
||||||
|
};
|
||||||
|
|
||||||
+9
-11
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
"use strict";
|
import * as path from "path";
|
||||||
import path from "path";
|
import * as fs from "fs";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ import * as cwmp from "../lib/cwmp";
|
|||||||
import * as db from "../lib/db";
|
import * as db from "../lib/db";
|
||||||
import * as extensions from "../lib/extensions";
|
import * as extensions from "../lib/extensions";
|
||||||
import * as cache from "../lib/cache";
|
import * as cache from "../lib/cache";
|
||||||
|
|
||||||
import { version as VERSION } from "../package.json";
|
import { version as VERSION } from "../package.json";
|
||||||
|
|
||||||
logger.init("cwmp", VERSION);
|
logger.init("cwmp", VERSION);
|
||||||
@@ -21,14 +19,14 @@ logger.init("cwmp", VERSION);
|
|||||||
const SERVICE_ADDRESS = config.get("CWMP_INTERFACE");
|
const SERVICE_ADDRESS = config.get("CWMP_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("CWMP_PORT");
|
const SERVICE_PORT = config.get("CWMP_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully() {
|
function exitWorkerGracefully(): void {
|
||||||
db.disconnect();
|
db.disconnect();
|
||||||
cache.disconnect();
|
cache.disconnect();
|
||||||
extensions.killAll();
|
extensions.killAll();
|
||||||
cluster.worker.disconnect();
|
cluster.worker.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully() {
|
function exitWorkerUngracefully(): void {
|
||||||
extensions.killAll(() => {
|
extensions.killAll(() => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
@@ -66,14 +64,14 @@ if (!cluster.worker) {
|
|||||||
let ssl;
|
let ssl;
|
||||||
if (config.get("CWMP_SSL")) {
|
if (config.get("CWMP_SSL")) {
|
||||||
ssl = {
|
ssl = {
|
||||||
key: path.resolve(config.get("CONFIG_DIR"), "cwmp.key"),
|
key: path.resolve("" + config.get("CONFIG_DIR"), "cwmp.key"),
|
||||||
cert: path.resolve(config.get("CONFIG_DIR"), "cwmp.crt"),
|
cert: path.resolve("" + config.get("CONFIG_DIR"), "cwmp.crt"),
|
||||||
ca: path.resolve(config.get("CONFIG_DIR"), "cwmp.ca-bundle")
|
ca: path.resolve("" + config.get("CONFIG_DIR"), "cwmp.ca-bundle")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("uncaughtException", err => {
|
process.on("uncaughtException", err => {
|
||||||
if (err.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,
|
||||||
|
|||||||
+6
-4
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2018 Zaid Abdulla
|
* Copyright 2013-2018 Zaid Abdulla
|
||||||
*
|
*
|
||||||
@@ -17,18 +18,19 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
import { Fault } from "../lib/types";
|
||||||
|
|
||||||
const jobs = new Set();
|
const jobs = new Set();
|
||||||
const fileName = process.argv[2];
|
const fileName = process.argv[2];
|
||||||
let script;
|
let script;
|
||||||
|
|
||||||
function errorToFault(err) {
|
function errorToFault(err: Error): Fault {
|
||||||
if (!err) return null;
|
if (!err) return null;
|
||||||
|
|
||||||
if (!err.name) return { code: "ext", message: `${err}` };
|
if (!err.name) return { code: "ext", message: `${err}` };
|
||||||
|
|
||||||
const fault = {
|
const fault: Fault = {
|
||||||
code: `ext.${err.name}`,
|
code: `ext.${err.name}`,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
detail: {
|
detail: {
|
||||||
|
|||||||
+9
-10
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
"use strict";
|
import * as path from "path";
|
||||||
import path from "path";
|
import * as fs from "fs";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
||||||
|
|
||||||
@@ -19,13 +18,13 @@ logger.init("fs", VERSION);
|
|||||||
const SERVICE_ADDRESS = config.get("FS_INTERFACE");
|
const SERVICE_ADDRESS = config.get("FS_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("FS_PORT");
|
const SERVICE_PORT = config.get("FS_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully() {
|
function exitWorkerGracefully(): void {
|
||||||
db.disconnect();
|
db.disconnect();
|
||||||
cache.disconnect();
|
cache.disconnect();
|
||||||
cluster.worker.disconnect();
|
cluster.worker.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully() {
|
function exitWorkerUngracefully(): void {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,14 +60,14 @@ if (!cluster.worker) {
|
|||||||
let ssl;
|
let ssl;
|
||||||
if (config.get("FS_SSL")) {
|
if (config.get("FS_SSL")) {
|
||||||
ssl = {
|
ssl = {
|
||||||
key: path.resolve(config.get("CONFIG_DIR"), "fs.key"),
|
key: path.resolve("" + config.get("CONFIG_DIR"), "fs.key"),
|
||||||
cert: path.resolve(config.get("CONFIG_DIR"), "fs.crt"),
|
cert: path.resolve("" + config.get("CONFIG_DIR"), "fs.crt"),
|
||||||
ca: path.resolve(config.get("CONFIG_DIR"), "fs.ca-bundle")
|
ca: path.resolve("" + config.get("CONFIG_DIR"), "fs.ca-bundle")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("uncaughtException", err => {
|
process.on("uncaughtException", err => {
|
||||||
if (err.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,
|
||||||
|
|||||||
+9
-10
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
"use strict";
|
import * as path from "path";
|
||||||
import path from "path";
|
import * as fs from "fs";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
||||||
|
|
||||||
@@ -20,14 +19,14 @@ logger.init("nbi", VERSION);
|
|||||||
const SERVICE_ADDRESS = config.get("NBI_INTERFACE");
|
const SERVICE_ADDRESS = config.get("NBI_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("NBI_PORT");
|
const SERVICE_PORT = config.get("NBI_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully() {
|
function exitWorkerGracefully(): void {
|
||||||
db.disconnect();
|
db.disconnect();
|
||||||
cache.disconnect();
|
cache.disconnect();
|
||||||
extensions.killAll();
|
extensions.killAll();
|
||||||
cluster.worker.disconnect();
|
cluster.worker.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully() {
|
function exitWorkerUngracefully(): void {
|
||||||
extensions.killAll(() => {
|
extensions.killAll(() => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
@@ -65,14 +64,14 @@ if (!cluster.worker) {
|
|||||||
let ssl;
|
let ssl;
|
||||||
if (config.get("NBI_SSL")) {
|
if (config.get("NBI_SSL")) {
|
||||||
ssl = {
|
ssl = {
|
||||||
key: path.resolve(config.get("CONFIG_DIR"), "nbi.key"),
|
key: path.resolve("" + config.get("CONFIG_DIR"), "nbi.key"),
|
||||||
cert: path.resolve(config.get("CONFIG_DIR"), "nbi.crt"),
|
cert: path.resolve("" + config.get("CONFIG_DIR"), "nbi.crt"),
|
||||||
ca: path.resolve(config.get("CONFIG_DIR"), "nbi.ca-bundle")
|
ca: path.resolve("" + config.get("CONFIG_DIR"), "nbi.ca-bundle")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("uncaughtException", err => {
|
process.on("uncaughtException", err => {
|
||||||
if (err.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,
|
||||||
|
|||||||
+8
-9
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
"use strict";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
|
||||||
@@ -20,14 +19,14 @@ logger.init("ui", VERSION);
|
|||||||
const SERVICE_ADDRESS = config.get("UI_INTERFACE");
|
const SERVICE_ADDRESS = config.get("UI_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("UI_PORT");
|
const SERVICE_PORT = config.get("UI_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully() {
|
function exitWorkerGracefully(): void {
|
||||||
db.disconnect();
|
db.disconnect();
|
||||||
db2.disconnect();
|
db2.disconnect();
|
||||||
cache.disconnect();
|
cache.disconnect();
|
||||||
cluster.worker.disconnect();
|
cluster.worker.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully() {
|
function exitWorkerUngracefully(): void {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,16 +62,16 @@ if (!cluster.worker) {
|
|||||||
let ssl;
|
let ssl;
|
||||||
if (config.get("UI_SSL")) {
|
if (config.get("UI_SSL")) {
|
||||||
ssl = {
|
ssl = {
|
||||||
key: path.resolve(config.get("CONFIG_DIR"), "ui.key"),
|
key: path.resolve("" + config.get("CONFIG_DIR"), "ui.key"),
|
||||||
cert: path.resolve(config.get("CONFIG_DIR"), "ui.crt"),
|
cert: path.resolve("" + config.get("CONFIG_DIR"), "ui.crt"),
|
||||||
ca: path.resolve(config.get("CONFIG_DIR"), "ui.ca-bundle")
|
ca: path.resolve("" + config.get("CONFIG_DIR"), "ui.ca-bundle")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let stopping = false;
|
let stopping = false;
|
||||||
|
|
||||||
process.on("uncaughtException", err => {
|
process.on("uncaughtException", err => {
|
||||||
if (err.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,
|
||||||
@@ -101,7 +100,7 @@ if (!cluster.worker) {
|
|||||||
.catch(exitWorkerUngracefully);
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
|
|
||||||
const _listener = (req, res) => {
|
const _listener = (req, res): void => {
|
||||||
if (stopping) res.setHeader("Connection", "close");
|
if (stopping) res.setHeader("Connection", "close");
|
||||||
listener(req, res);
|
listener(req, res);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
const path = require("path");
|
import * as path from "path";
|
||||||
const fs = require("fs");
|
import * as fs from "fs";
|
||||||
const { promisify } = require("util");
|
import { promisify } from "util";
|
||||||
const { rollup } = require("rollup");
|
import { rollup, WarningHandler } from "rollup";
|
||||||
const rollupReplace = require("rollup-plugin-replace");
|
import rollupReplace from "rollup-plugin-replace";
|
||||||
const rollupJson = require("rollup-plugin-json");
|
import rollupJson from "rollup-plugin-json";
|
||||||
const { terser } = require("rollup-plugin-terser");
|
import typescript from "rollup-plugin-typescript";
|
||||||
const webpack = require("webpack");
|
import { terser } from "rollup-plugin-terser";
|
||||||
const postcss = require("postcss");
|
import webpack from "webpack";
|
||||||
const postcssImport = require("postcss-import");
|
import postcss from "postcss";
|
||||||
const postcssCssNext = require("postcss-cssnext");
|
import postcssImport from "postcss-import";
|
||||||
const cssnano = require("cssnano");
|
import postcssCssNext from "postcss-cssnext";
|
||||||
|
import cssnano from "cssnano";
|
||||||
|
|
||||||
const MODE = "production";
|
const MODE = process.env["NODE_ENV"] || "production";
|
||||||
|
|
||||||
const BUILD_METADATA = new Date()
|
const BUILD_METADATA = new Date()
|
||||||
.toISOString()
|
.toISOString()
|
||||||
@@ -49,10 +50,12 @@ const externals = [
|
|||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"stream",
|
"stream",
|
||||||
"mithril",
|
"mithril",
|
||||||
"parsimmon"
|
"parsimmon",
|
||||||
|
"codemirror",
|
||||||
|
"codemirror/mode/javascript/javascript"
|
||||||
];
|
];
|
||||||
|
|
||||||
function rmDirSync(dirPath) {
|
function rmDirSync(dirPath): void {
|
||||||
if (!fs.existsSync(dirPath)) return;
|
if (!fs.existsSync(dirPath)) return;
|
||||||
const files = fs.readdirSync(dirPath);
|
const files = fs.readdirSync(dirPath);
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ function rmDirSync(dirPath) {
|
|||||||
fs.rmdirSync(dirPath);
|
fs.rmdirSync(dirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init(): Promise<void> {
|
||||||
// Delete any old output directory
|
// Delete any old output directory
|
||||||
rmDirSync(OUTPUT_DIR);
|
rmDirSync(OUTPUT_DIR);
|
||||||
|
|
||||||
@@ -77,7 +80,9 @@ async function init() {
|
|||||||
fs.mkdirSync(OUTPUT_DIR + "/tools");
|
fs.mkdirSync(OUTPUT_DIR + "/tools");
|
||||||
|
|
||||||
// Create package.json
|
// Create package.json
|
||||||
const packageJson = require("../package.json");
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(INPUT_DIR, "package.json")).toString()
|
||||||
|
);
|
||||||
delete packageJson["devDependencies"];
|
delete packageJson["devDependencies"];
|
||||||
packageJson["scripts"] = {
|
packageJson["scripts"] = {
|
||||||
install: packageJson["scripts"].install,
|
install: packageJson["scripts"].install,
|
||||||
@@ -90,7 +95,7 @@ async function init() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyStatic() {
|
async function copyStatic(): Promise<void> {
|
||||||
const files = [
|
const files = [
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"README.md",
|
"README.md",
|
||||||
@@ -111,7 +116,7 @@ async function copyStatic() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateCss() {
|
async function generateCss(): Promise<void> {
|
||||||
const cssInPath = path.resolve(INPUT_DIR, "ui/css/app.css");
|
const cssInPath = path.resolve(INPUT_DIR, "ui/css/app.css");
|
||||||
const cssOutPath = path.resolve(OUTPUT_DIR, "public/app.css");
|
const cssOutPath = path.resolve(OUTPUT_DIR, "public/app.css");
|
||||||
const cssIn = fs.readFileSync(cssInPath);
|
const cssIn = fs.readFileSync(cssInPath);
|
||||||
@@ -123,7 +128,7 @@ async function generateCss() {
|
|||||||
fs.writeFileSync(cssOutPath, cssOut.css);
|
fs.writeFileSync(cssOutPath, cssOut.css);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateToolsJs() {
|
async function generateToolsJs(): Promise<void> {
|
||||||
for (const bin of ["configure-ui", "dump-data-model"]) {
|
for (const bin of ["configure-ui", "dump-data-model"]) {
|
||||||
const inputFile = path.resolve(INPUT_DIR, `tools/${bin}`);
|
const inputFile = path.resolve(INPUT_DIR, `tools/${bin}`);
|
||||||
const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`);
|
const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`);
|
||||||
@@ -136,7 +141,11 @@ async function generateToolsJs() {
|
|||||||
plugins: [
|
plugins: [
|
||||||
rollupReplace({
|
rollupReplace({
|
||||||
delimiters: ["", ""],
|
delimiters: ["", ""],
|
||||||
"#!/usr/bin/env -S node -r esm": ""
|
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
|
||||||
|
}),
|
||||||
|
typescript({
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
include: [`tools/${bin}`, "lib/**/*.ts"]
|
||||||
}),
|
}),
|
||||||
MODE === "production" ? terser() : null
|
MODE === "production" ? terser() : null
|
||||||
]
|
]
|
||||||
@@ -155,7 +164,7 @@ async function generateToolsJs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateBackendJs() {
|
async function generateBackendJs(): Promise<void> {
|
||||||
for (const bin of [
|
for (const bin of [
|
||||||
"genieacs-cwmp",
|
"genieacs-cwmp",
|
||||||
"genieacs-ext",
|
"genieacs-ext",
|
||||||
@@ -178,7 +187,7 @@ async function generateBackendJs() {
|
|||||||
plugins: [
|
plugins: [
|
||||||
rollupReplace({
|
rollupReplace({
|
||||||
delimiters: ["", ""],
|
delimiters: ["", ""],
|
||||||
"#!/usr/bin/env -S node -r esm": ""
|
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
|
||||||
}),
|
}),
|
||||||
rollupJson({ preferConst: true }),
|
rollupJson({ preferConst: true }),
|
||||||
{
|
{
|
||||||
@@ -191,6 +200,10 @@ async function generateBackendJs() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
typescript({
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
include: [`bin/${bin}`, "lib/**/*.ts"]
|
||||||
|
}),
|
||||||
MODE === "production" ? terser() : null
|
MODE === "production" ? terser() : null
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -208,23 +221,26 @@ async function generateBackendJs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateFrontendJs() {
|
async function generateFrontendJs(): Promise<void> {
|
||||||
const inputFile = path.resolve(INPUT_DIR, "ui/app.js");
|
const inputFile = path.resolve(INPUT_DIR, "ui/app.ts");
|
||||||
const outputFile = path.resolve(OUTPUT_DIR, "public/app.js");
|
const outputFile = path.resolve(OUTPUT_DIR, "public/app.js");
|
||||||
|
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: inputFile,
|
input: inputFile,
|
||||||
external: externals,
|
external: externals,
|
||||||
plugins: [rollupJson({ preferConst: true })],
|
plugins: [
|
||||||
|
rollupJson({ preferConst: true }),
|
||||||
|
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
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.write({
|
||||||
@@ -236,6 +252,9 @@ async function generateFrontendJs() {
|
|||||||
const webpackConf = {
|
const webpackConf = {
|
||||||
mode: MODE,
|
mode: MODE,
|
||||||
entry: outputFile,
|
entry: outputFile,
|
||||||
|
resolve: {
|
||||||
|
aliasFields: ["module"]
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(OUTPUT_DIR, "public"),
|
path: path.resolve(OUTPUT_DIR, "public"),
|
||||||
filename: "app.js"
|
filename: "app.js"
|
||||||
@@ -16,19 +16,18 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import dgram from "dgram";
|
import * as dgram from "dgram";
|
||||||
import URL from "url";
|
import * as URL from "url";
|
||||||
import http from "http";
|
import * as http from "http";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import * as auth from "./auth";
|
import * as auth from "./auth";
|
||||||
import * as cache from "./cache";
|
import * as cache from "./cache";
|
||||||
|
|
||||||
function udpConReq(address, un, key, callback) {
|
function udpConReq(address, un, key, callback): boolean {
|
||||||
if (!address) return false;
|
if (!address) return false;
|
||||||
|
|
||||||
let [host, port] = address.split(":", 2);
|
let [host, port] = address.split(":", 2);
|
||||||
@@ -53,7 +52,9 @@ function udpConReq(address, un, key, callback) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const client = dgram.createSocket({ type: "udp4", reuseAddr: true });
|
const client = dgram.createSocket({ type: "udp4", reuseAddr: true });
|
||||||
const UDP_CONNECTION_REQUEST_PORT = config.get("UDP_CONNECTION_REQUEST_PORT");
|
const UDP_CONNECTION_REQUEST_PORT = +config.get(
|
||||||
|
"UDP_CONNECTION_REQUEST_PORT"
|
||||||
|
);
|
||||||
|
|
||||||
if (UDP_CONNECTION_REQUEST_PORT)
|
if (UDP_CONNECTION_REQUEST_PORT)
|
||||||
// When a device is NAT'ed, the UDP Connection Request must originate from
|
// When a device is NAT'ed, the UDP Connection Request must originate from
|
||||||
@@ -65,7 +66,7 @@ function udpConReq(address, un, key, callback) {
|
|||||||
client.bind({ port: UDP_CONNECTION_REQUEST_PORT, exclusive: true });
|
client.bind({ port: UDP_CONNECTION_REQUEST_PORT, exclusive: true });
|
||||||
|
|
||||||
let count = 3;
|
let count = 3;
|
||||||
const func = err => {
|
const func = (err: Error): void => {
|
||||||
if (err || --count <= 0) {
|
if (err || --count <= 0) {
|
||||||
client.close();
|
client.close();
|
||||||
return void callback(err);
|
return void callback(err);
|
||||||
@@ -83,8 +84,8 @@ function httpConReq(
|
|||||||
allowBasicAuth,
|
allowBasicAuth,
|
||||||
timeout,
|
timeout,
|
||||||
callback
|
callback
|
||||||
) {
|
): void {
|
||||||
const options = URL.parse(url);
|
const options: http.RequestOptions = URL.parse(url);
|
||||||
if (options.protocol !== "http:") {
|
if (options.protocol !== "http:") {
|
||||||
return void callback(
|
return void callback(
|
||||||
new Error("Invalid connection request URL or protocol")
|
new Error("Invalid connection request URL or protocol")
|
||||||
@@ -92,7 +93,7 @@ function httpConReq(
|
|||||||
}
|
}
|
||||||
options.agent = new http.Agent({ maxSockets: 1 });
|
options.agent = new http.Agent({ maxSockets: 1 });
|
||||||
|
|
||||||
function statusToError(statusCode) {
|
function statusToError(statusCode): Error {
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
case 204:
|
case 204:
|
||||||
@@ -112,7 +113,7 @@ function httpConReq(
|
|||||||
const authHeader = auth.parseAuthHeader(
|
const authHeader = auth.parseAuthHeader(
|
||||||
response.headers["www-authenticate"]
|
response.headers["www-authenticate"]
|
||||||
);
|
);
|
||||||
if (authHeader.method === "Basic") {
|
if (authHeader["method"] === "Basic") {
|
||||||
if (!allowBasicAuth) {
|
if (!allowBasicAuth) {
|
||||||
request.abort();
|
request.abort();
|
||||||
return void callback(
|
return void callback(
|
||||||
@@ -122,7 +123,7 @@ function httpConReq(
|
|||||||
options.headers = {
|
options.headers = {
|
||||||
Authorization: auth.basic(username || "", password || "")
|
Authorization: auth.basic(username || "", password || "")
|
||||||
};
|
};
|
||||||
} else if (authHeader.method === "Digest") {
|
} else if (authHeader["method"] === "Digest") {
|
||||||
options.headers = {
|
options.headers = {
|
||||||
Authorization: auth.digest(
|
Authorization: auth.digest(
|
||||||
username || "",
|
username || "",
|
||||||
@@ -187,7 +188,7 @@ function httpConReq(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectionRequest(deviceId, callback) {
|
export function connectionRequest(deviceId, callback): void {
|
||||||
const CONNECTION_REQUEST_TIMEOUT = config.get(
|
const CONNECTION_REQUEST_TIMEOUT = config.get(
|
||||||
"CONNECTION_REQUEST_TIMEOUT",
|
"CONNECTION_REQUEST_TIMEOUT",
|
||||||
deviceId
|
deviceId
|
||||||
@@ -235,7 +236,7 @@ export function connectionRequest(deviceId, callback) {
|
|||||||
if (managementServer.ConnectionRequestPassword)
|
if (managementServer.ConnectionRequestPassword)
|
||||||
password = managementServer.ConnectionRequestPassword._value;
|
password = managementServer.ConnectionRequestPassword._value;
|
||||||
|
|
||||||
const conReq = () => {
|
const conReq = (): void => {
|
||||||
const udpSent = udpConReq(
|
const udpSent = udpConReq(
|
||||||
udpConnectionRequestAddress,
|
udpConnectionRequestAddress,
|
||||||
username,
|
username,
|
||||||
@@ -284,7 +285,7 @@ export function connectionRequest(deviceId, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function watchTask(deviceId, taskId, timeout, callback) {
|
export function watchTask(deviceId, taskId, timeout, callback): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
db.tasksCollection.findOne(
|
db.tasksCollection.findOne(
|
||||||
{ _id: taskId },
|
{ _id: taskId },
|
||||||
@@ -313,7 +314,7 @@ export function watchTask(deviceId, taskId, timeout, callback) {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeTask(task, callback) {
|
function sanitizeTask(task, callback): 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 (common.typeOf(task.expiry) === common.DATE_TYPE || isNaN(task.expiry))
|
||||||
@@ -324,7 +325,7 @@ function sanitizeTask(task, callback) {
|
|||||||
callback(task);
|
callback(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertTasks(tasks, callback) {
|
export function insertTasks(tasks, callback): void {
|
||||||
if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) tasks = [tasks];
|
if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) tasks = [tasks];
|
||||||
else if (!tasks || tasks.length === 0)
|
else if (!tasks || tasks.length === 0)
|
||||||
return void callback(null, tasks || []);
|
return void callback(null, tasks || []);
|
||||||
@@ -344,7 +345,7 @@ export function insertTasks(tasks, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteDevice(deviceId, callback) {
|
export function deleteDevice(deviceId, callback): void {
|
||||||
db.tasksCollection.deleteMany({ device: deviceId }, err => {
|
db.tasksCollection.deleteMany({ device: deviceId }, err => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
db.devicesCollection.deleteOne({ _id: deviceId }, err => {
|
db.devicesCollection.deleteOne({ _id: deviceId }, err => {
|
||||||
+16
-10
@@ -16,11 +16,10 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
|
|
||||||
export function parseAuthHeader(authHeader) {
|
export function parseAuthHeader(authHeader): {} {
|
||||||
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 };
|
||||||
@@ -53,11 +52,18 @@ export function parseAuthHeader(authHeader) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function basic(username, password) {
|
export function basic(username, password): string {
|
||||||
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function digest(username, password, uri, httpMethod, body, authHeader) {
|
export function digest(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
uri,
|
||||||
|
httpMethod,
|
||||||
|
body,
|
||||||
|
authHeader
|
||||||
|
): string {
|
||||||
const cnonce = "0a4f113b";
|
const cnonce = "0a4f113b";
|
||||||
const nc = "00000001";
|
const nc = "00000001";
|
||||||
let qop;
|
let qop;
|
||||||
@@ -68,7 +74,7 @@ export function digest(username, password, uri, httpMethod, body, authHeader) {
|
|||||||
else qop = authHeader.qop;
|
else qop = authHeader.qop;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ha1 = createHash("md5");
|
const ha1 = createHash("md5");
|
||||||
ha1
|
ha1
|
||||||
.update(username)
|
.update(username)
|
||||||
.update(":")
|
.update(":")
|
||||||
@@ -76,19 +82,19 @@ export function digest(username, password, uri, httpMethod, body, authHeader) {
|
|||||||
.update(":")
|
.update(":")
|
||||||
.update(password);
|
.update(password);
|
||||||
// TODO support "MD5-sess" algorithm directive
|
// TODO support "MD5-sess" algorithm directive
|
||||||
ha1 = ha1.digest("hex");
|
const ha1d = ha1.digest("hex");
|
||||||
|
|
||||||
let ha2 = createHash("md5");
|
const ha2 = createHash("md5");
|
||||||
ha2
|
ha2
|
||||||
.update(httpMethod)
|
.update(httpMethod)
|
||||||
.update(":")
|
.update(":")
|
||||||
.update(uri);
|
.update(uri);
|
||||||
if (qop === "auth-int") ha2.update(":").update(body);
|
if (qop === "auth-int") ha2.update(":").update(body);
|
||||||
ha2 = ha2.digest("hex");
|
const ha2d = ha2.digest("hex");
|
||||||
|
|
||||||
const hash = createHash("md5");
|
const hash = createHash("md5");
|
||||||
hash
|
hash
|
||||||
.update(ha1)
|
.update(ha1d)
|
||||||
.update(":")
|
.update(":")
|
||||||
.update(authHeader.nonce);
|
.update(authHeader.nonce);
|
||||||
if (qop) {
|
if (qop) {
|
||||||
@@ -100,7 +106,7 @@ export function digest(username, password, uri, httpMethod, body, authHeader) {
|
|||||||
.update(":")
|
.update(":")
|
||||||
.update(qop);
|
.update(qop);
|
||||||
}
|
}
|
||||||
hash.update(":").update(ha2);
|
hash.update(":").update(ha2d);
|
||||||
|
|
||||||
let authString = `Digest username="${username}"`;
|
let authString = `Digest username="${username}"`;
|
||||||
authString += `,realm="${authHeader.realm}"`;
|
authString += `,realm="${authHeader.realm}"`;
|
||||||
+31
-14
@@ -16,19 +16,18 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
|
|
||||||
const MAX_CACHE_TTL = config.get("MAX_CACHE_TTL");
|
const MAX_CACHE_TTL = +config.get("MAX_CACHE_TTL");
|
||||||
|
|
||||||
let mongoClient;
|
let mongoClient;
|
||||||
let mongoCollection;
|
let mongoCollection;
|
||||||
let mongoTimeOffset = 0;
|
let mongoTimeOffset = 0;
|
||||||
|
|
||||||
export function connect(callback) {
|
export function connect(callback): void {
|
||||||
const MONGODB_CONNECTION_URL = config.get("MONGODB_CONNECTION_URL");
|
const MONGODB_CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL");
|
||||||
|
|
||||||
MongoClient.connect(
|
MongoClient.connect(
|
||||||
MONGODB_CONNECTION_URL,
|
MONGODB_CONNECTION_URL,
|
||||||
@@ -51,11 +50,11 @@ export function connect(callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disconnect() {
|
export function disconnect(): void {
|
||||||
if (mongoClient) mongoClient.close();
|
if (mongoClient) mongoClient.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get(key, callback) {
|
export function get(key, callback): void {
|
||||||
const expire = new Date(Date.now() - mongoTimeOffset);
|
const expire = new Date(Date.now() - mongoTimeOffset);
|
||||||
if (Array.isArray(key)) {
|
if (Array.isArray(key)) {
|
||||||
mongoCollection.find({ _id: { $in: key } }).toArray((err, res) => {
|
mongoCollection.find({ _id: { $in: key } }).toArray((err, res) => {
|
||||||
@@ -83,21 +82,39 @@ export function get(key, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function del(key, callback) {
|
export function del(key, callback): void {
|
||||||
if (Array.isArray(key))
|
if (Array.isArray(key))
|
||||||
mongoCollection.deleteMany({ _id: { $in: key } }, callback);
|
mongoCollection.deleteMany({ _id: { $in: key } }, callback);
|
||||||
else mongoCollection.deleteOne({ _id: key }, callback);
|
else mongoCollection.deleteOne({ _id: key }, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function set(key, value, ttl, callback) {
|
export function set(
|
||||||
if (!callback && typeof v === "function") {
|
key: string,
|
||||||
|
value: string | number,
|
||||||
|
callback: (err?: Error) => void
|
||||||
|
): void;
|
||||||
|
export function set(
|
||||||
|
key: string,
|
||||||
|
value: string | number,
|
||||||
|
ttl: number,
|
||||||
|
callback: (err?: Error) => void
|
||||||
|
): void;
|
||||||
|
export function set(
|
||||||
|
key: string,
|
||||||
|
value: string | number,
|
||||||
|
ttl: number | ((err?: Error) => void),
|
||||||
|
callback?: (err?: Error) => void
|
||||||
|
): void {
|
||||||
|
if (!callback && typeof ttl === "function") {
|
||||||
callback = ttl;
|
callback = ttl;
|
||||||
ttl = null;
|
ttl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ttl) ttl = MAX_CACHE_TTL;
|
if (!ttl) ttl = MAX_CACHE_TTL;
|
||||||
|
|
||||||
const expire = new Date(Date.now() - mongoTimeOffset + ttl * 1000);
|
const expire = new Date(
|
||||||
|
Date.now() - mongoTimeOffset + (ttl as number) * 1000
|
||||||
|
);
|
||||||
mongoCollection.replaceOne(
|
mongoCollection.replaceOne(
|
||||||
{ _id: key },
|
{ _id: key },
|
||||||
{ _id: key, value: value, expire: expire },
|
{ _id: key, value: value, expire: expire },
|
||||||
@@ -106,7 +123,7 @@ export function set(key, value, ttl, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pop(key, callback) {
|
export function pop(key, callback): void {
|
||||||
mongoCollection.findOneAndDelete({ _id: key }, (err, res) => {
|
mongoCollection.findOneAndDelete({ _id: key }, (err, res) => {
|
||||||
if (err || !res["value"]) return void callback(err);
|
if (err || !res["value"]) return void callback(err);
|
||||||
|
|
||||||
@@ -117,12 +134,12 @@ export function pop(key, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lock(lockName, ttl, callback) {
|
export function lock(lockName, ttl, callback): void {
|
||||||
const token = Math.random()
|
const token = Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.slice(2);
|
.slice(2);
|
||||||
|
|
||||||
function unlockOrExtend(extendTtl) {
|
function unlockOrExtend(extendTtl): void {
|
||||||
if (!extendTtl) {
|
if (!extendTtl) {
|
||||||
mongoCollection.deleteOne({ _id: lockName, value: token }, (err, res) => {
|
mongoCollection.deleteOne({ _id: lockName, value: token }, (err, res) => {
|
||||||
if (err || res["result"]["n"] !== 1)
|
if (err || res["result"]["n"] !== 1)
|
||||||
@@ -135,7 +152,7 @@ export function lock(lockName, ttl, callback) {
|
|||||||
{ expire: expire },
|
{ expire: expire },
|
||||||
(err, res) => {
|
(err, res) => {
|
||||||
if (err || res["result"]["n"] !== 1)
|
if (err || res["result"]["n"] !== 1)
|
||||||
throw err | new Error("Lock expired");
|
throw err || new Error("Lock expired");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import cluster from "cluster";
|
import cluster from "cluster";
|
||||||
import { cpus } from "os";
|
import { cpus } from "os";
|
||||||
@@ -25,10 +24,12 @@ import * as logger from "./logger";
|
|||||||
let respawnTimestamp = 0;
|
let respawnTimestamp = 0;
|
||||||
let tooManyCrashesTimestamp = 0;
|
let tooManyCrashesTimestamp = 0;
|
||||||
|
|
||||||
function restartWorker(worker, code, signal) {
|
function restartWorker(worker, code, signal): void {
|
||||||
const msg = {
|
const msg = {
|
||||||
message: "Worker died",
|
message: "Worker died",
|
||||||
pid: worker.process.pid
|
pid: worker.process.pid,
|
||||||
|
exitCode: null,
|
||||||
|
signal: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (code != null) msg.exitCode = code;
|
if (code != null) msg.exitCode = code;
|
||||||
@@ -63,7 +64,7 @@ function restartWorker(worker, code, signal) {
|
|||||||
}, respawnTimestamp - now);
|
}, respawnTimestamp - now);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(workerCount, servicePort, serviceAddress) {
|
export function start(workerCount, servicePort, serviceAddress): 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) &&
|
||||||
@@ -86,7 +87,7 @@ export function start(workerCount, servicePort, serviceAddress) {
|
|||||||
for (let i = 0; i < workerCount; ++i) cluster.fork();
|
for (let i = 0; i < workerCount; ++i) cluster.fork();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stop() {
|
export function stop(): void {
|
||||||
cluster.removeListener("exit", restartWorker);
|
cluster.removeListener("exit", restartWorker);
|
||||||
for (const pid in cluster.workers) cluster.workers[pid].kill();
|
for (const pid in cluster.workers) cluster.workers[pid].kill();
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
import { ParamPath } from "./types";
|
||||||
|
|
||||||
export const UNDEFINED_TYPE = "[object Undefined]";
|
export const UNDEFINED_TYPE = "[object Undefined]";
|
||||||
export const NULL_TYPE = "[object Null]";
|
export const NULL_TYPE = "[object Null]";
|
||||||
@@ -28,11 +29,11 @@ export const OBJECT_TYPE = "[object Object]";
|
|||||||
export const REGEXP_TYPE = "[object RegExp]";
|
export const REGEXP_TYPE = "[object RegExp]";
|
||||||
export const DATE_TYPE = "[object Date]";
|
export const DATE_TYPE = "[object Date]";
|
||||||
|
|
||||||
export const typeOf = obj => Object.prototype.toString.call(obj);
|
export const typeOf = (obj): string => Object.prototype.toString.call(obj);
|
||||||
|
|
||||||
export function generateDeviceId(deviceIdStruct) {
|
export function generateDeviceId(deviceIdStruct): string {
|
||||||
// Escapes everything except alphanumerics and underscore
|
// Escapes everything except alphanumerics and underscore
|
||||||
function esc(str) {
|
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 = "";
|
||||||
@@ -54,11 +55,12 @@ export function generateDeviceId(deviceIdStruct) {
|
|||||||
return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]);
|
return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAlias(pattern, start, res) {
|
export function parseAlias(pattern, start, res): number {
|
||||||
const aliases = [];
|
const aliases = [];
|
||||||
let i = start;
|
let i = start;
|
||||||
while (i < pattern.length && pattern[i] !== "]") {
|
while (i < pattern.length && pattern[i] !== "]") {
|
||||||
const alias = [];
|
const alias = [];
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
||||||
let j = (i = parsePath(pattern, i, alias) + 1);
|
let j = (i = parsePath(pattern, i, alias) + 1);
|
||||||
while (pattern[j] !== "]" && pattern[j] !== ",") {
|
while (pattern[j] !== "]" && pattern[j] !== ",") {
|
||||||
if (pattern[j] === '"' && i === j) {
|
if (pattern[j] === '"' && i === j) {
|
||||||
@@ -87,7 +89,7 @@ export function parseAlias(pattern, start, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort to ensure identical expressions have idential string representation
|
// Sort to ensure identical expressions have idential string representation
|
||||||
function srt(a, b) {
|
function srt(a, b): number {
|
||||||
const jMax = Math.min(a.length, b.length);
|
const jMax = Math.min(a.length, b.length);
|
||||||
for (let j = 0; j < jMax; j += 2) {
|
for (let j = 0; j < jMax; j += 2) {
|
||||||
const kMax = Math.min(a[j].length, b[j].length);
|
const kMax = Math.min(a[j].length, b[j].length);
|
||||||
@@ -126,8 +128,11 @@ export function parseAlias(pattern, start, res) {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePath(pattern, start, res) {
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
const path = [];
|
export function parsePath(pattern, start, res): number;
|
||||||
|
export function parsePath(pattern, start?): ParamPath;
|
||||||
|
export function parsePath(pattern, start?, res?): number | ParamPath {
|
||||||
|
const path = [] as ParamPath;
|
||||||
path.wildcard = 0;
|
path.wildcard = 0;
|
||||||
path.alias = 0;
|
path.alias = 0;
|
||||||
let i = start || 0;
|
let i = start || 0;
|
||||||
@@ -163,7 +168,7 @@ export function parsePath(pattern, start, res) {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPathMeta(path) {
|
export function addPathMeta(path): ParamPath {
|
||||||
if (path.alias != null && path.wildcard != null) return path;
|
if (path.alias != null && path.wildcard != null) return path;
|
||||||
|
|
||||||
path.alias = 0;
|
path.alias = 0;
|
||||||
@@ -183,6 +188,6 @@ export function addPathMeta(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Source: http://stackoverflow.com/a/6969486
|
// Source: http://stackoverflow.com/a/6969486
|
||||||
export function escapeRegExp(str) {
|
export function escapeRegExp(str): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import pass from "./validators/pass";
|
import pass from "./validators/pass";
|
||||||
import test from "./validators/test";
|
import test from "./validators/test";
|
||||||
|
import { PermissionSet } from "../types";
|
||||||
|
|
||||||
const validators = { pass, test };
|
const validators = { pass, test };
|
||||||
|
|
||||||
export default class Authorizer {
|
export default class Authorizer {
|
||||||
constructor(permissionSets) {
|
private permissionSets: PermissionSet[];
|
||||||
|
private validatorCache: WeakMap<
|
||||||
|
object,
|
||||||
|
(mutationType, mutation, any) => boolean
|
||||||
|
>;
|
||||||
|
private hasAccessCache: Map<string, boolean>;
|
||||||
|
|
||||||
|
public constructor(permissionSets) {
|
||||||
this.permissionSets = permissionSets;
|
this.permissionSets = permissionSets;
|
||||||
this.validatorCache = new WeakMap();
|
this.validatorCache = new WeakMap();
|
||||||
this.hasAccessCache = {};
|
this.hasAccessCache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAccess(resourceType, access) {
|
public hasAccess(resourceType, access): boolean {
|
||||||
const cacheKey = `${resourceType}-${access}`;
|
const cacheKey = `${resourceType}-${access}`;
|
||||||
if (cacheKey in this.hasAccessCache) return this.hasAccessCache[cacheKey];
|
if (this.hasAccessCache.has(cacheKey))
|
||||||
|
return this.hasAccessCache.get(cacheKey);
|
||||||
|
|
||||||
let has = false;
|
let has = false;
|
||||||
for (const permissionSet of this.permissionSets) {
|
for (const permissionSet of this.permissionSets) {
|
||||||
@@ -28,11 +35,14 @@ export default class Authorizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasAccessCache[cacheKey] = has;
|
this.hasAccessCache.set(cacheKey, has);
|
||||||
return has;
|
return has;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidator(resourceType, resource) {
|
public getValidator(
|
||||||
|
resourceType,
|
||||||
|
resource
|
||||||
|
): (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);
|
||||||
|
|
||||||
@@ -53,7 +63,11 @@ export default class Authorizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validator = (mutationType, mutation, any) => {
|
const validator = (
|
||||||
|
mutationType: string,
|
||||||
|
mutation: any,
|
||||||
|
any: any
|
||||||
|
): boolean => {
|
||||||
let valid = false;
|
let valid = false;
|
||||||
for (const [k, v] of Object.entries(funcs)) {
|
for (const [k, v] of Object.entries(funcs)) {
|
||||||
if (v) {
|
if (v) {
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { map, parseLikePattern } from "./expression-parser";
|
import { map, parseLikePattern } from "./expression-parser";
|
||||||
|
import { Expression } from "../types";
|
||||||
|
|
||||||
function or(cnf1, cnf2) {
|
type CNF = number[][];
|
||||||
|
|
||||||
|
function or(cnf1: CNF, cnf2: CNF): CNF {
|
||||||
if (cnf1.length === 0) return cnf2;
|
if (cnf1.length === 0) return cnf2;
|
||||||
else if (cnf2.length === 0) return cnf1;
|
else if (cnf2.length === 0) return cnf1;
|
||||||
|
|
||||||
@@ -19,7 +20,30 @@ function or(cnf1, cnf2) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function booleanCnf(exp) {
|
function likePatternIncludes(pat1, pat2, idx1 = 0, idx2 = 0): boolean {
|
||||||
|
while (idx1 < pat1.length && idx2 < pat2.length) {
|
||||||
|
if (pat1[idx1] === "\\%") {
|
||||||
|
for (let i = idx2; i <= pat2.length; ++i)
|
||||||
|
if (likePatternIncludes(pat1, pat2, idx1 + 1, i)) return true;
|
||||||
|
return false;
|
||||||
|
} else if (pat2[idx2] === "\\%") {
|
||||||
|
if (pat1[idx1] !== "\\%") return false;
|
||||||
|
} else if (pat1[idx1] === "\\_") {
|
||||||
|
// Ignore
|
||||||
|
} else if (pat2[idx2] === "\\_") {
|
||||||
|
return false;
|
||||||
|
} else if (pat2[idx2] !== pat1[idx1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++idx1;
|
||||||
|
++idx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx1 === pat1.length && idx2 === pat2.length) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => {
|
||||||
@@ -51,15 +75,20 @@ export function booleanCnf(exp) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const [k, v] of ranges) {
|
for (const [k, v] of ranges) {
|
||||||
if (k.endsWith(":number"))
|
if (k.endsWith(":number")) {
|
||||||
ranges.set(k, Array.from(v).sort((a, b) => a - b));
|
ranges.set(
|
||||||
else ranges.set(k, Array.from(v).sort());
|
k,
|
||||||
|
Array.from(v as number[]).sort((a: number, b: number) => a - b)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ranges.set(k, Array.from(v).sort());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const expressions = new Map();
|
const expressions: Map<string, number> = new Map();
|
||||||
const mutuallyExclusive = new Map();
|
const mutuallyExclusive: Map<string, Set<number>> = new Map();
|
||||||
|
|
||||||
function getVariable(lhs, op, rhs = "") {
|
function getVariable(lhs, op, rhs = ""): number {
|
||||||
const key = `${lhs}:${op}:${rhs}`;
|
const key = `${lhs}:${op}:${rhs}`;
|
||||||
let f = expressions.get(key);
|
let f = expressions.get(key);
|
||||||
if (!f) {
|
if (!f) {
|
||||||
@@ -72,7 +101,13 @@ export function booleanCnf(exp) {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComparisonVariables(lhs, from, fromInclusive, to, toInclusive) {
|
function getComparisonVariables(
|
||||||
|
lhs,
|
||||||
|
from,
|
||||||
|
fromInclusive,
|
||||||
|
to,
|
||||||
|
toInclusive
|
||||||
|
): number[] {
|
||||||
const type = from != null ? typeof from : typeof to;
|
const type = from != null ? typeof from : typeof to;
|
||||||
const range = ranges.get(`${lhs}:${type}`);
|
const range = ranges.get(`${lhs}:${type}`);
|
||||||
const vars = [];
|
const vars = [];
|
||||||
@@ -109,7 +144,7 @@ export function booleanCnf(exp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLikeVariables(lhs, pat, esc) {
|
function getLikeVariables(lhs, pat, esc): number[] {
|
||||||
const vars = [];
|
const vars = [];
|
||||||
const pats = likePatterns.get(lhs);
|
const pats = likePatterns.get(lhs);
|
||||||
pat = pats.get(`${esc || ""}:${pat}`);
|
pat = pats.get(`${esc || ""}:${pat}`);
|
||||||
@@ -125,7 +160,11 @@ export function booleanCnf(exp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const comparisonOperators = {
|
interface ComparisonOperators {
|
||||||
|
[name: string]: (v: string) => [string, boolean, string, boolean];
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
@@ -133,7 +172,7 @@ export function booleanCnf(exp) {
|
|||||||
"<=": v => [null, null, v, true]
|
"<=": v => [null, null, v, true]
|
||||||
};
|
};
|
||||||
|
|
||||||
function recursive(clause, negate) {
|
function recursive(clause, negate): CNF {
|
||||||
let op = clause[0];
|
let op = clause[0];
|
||||||
|
|
||||||
if ((op === "AND" && !negate) || (op === "OR" && negate)) {
|
if ((op === "AND" && !negate) || (op === "OR" && negate)) {
|
||||||
@@ -218,33 +257,10 @@ export function booleanCnf(exp) {
|
|||||||
const cnf = recursive(exp, false);
|
const cnf = recursive(exp, false);
|
||||||
|
|
||||||
for (const m of mutuallyExclusive.values()) {
|
for (const m of mutuallyExclusive.values()) {
|
||||||
const ar = Array.from(m);
|
const ar: number[] = Array.from(m);
|
||||||
for (let i = 0; i < ar.length; ++i)
|
for (let i = 0; i < ar.length; ++i)
|
||||||
for (let j = i + 1; j < ar.length; ++j) cnf.push([0 - ar[i], 0 - ar[j]]);
|
for (let j = i + 1; j < ar.length; ++j) cnf.push([0 - ar[i], 0 - ar[j]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { vars: expressions.size, clauses: cnf };
|
return { vars: expressions.size, clauses: cnf };
|
||||||
}
|
}
|
||||||
|
|
||||||
function likePatternIncludes(pat1, pat2, idx1 = 0, idx2 = 0) {
|
|
||||||
while (idx1 < pat1.length && idx2 < pat2.length) {
|
|
||||||
if (pat1[idx1] === "\\%") {
|
|
||||||
for (let i = idx2; i <= pat2.length; ++i)
|
|
||||||
if (likePatternIncludes(pat1, pat2, idx1 + 1, i)) return true;
|
|
||||||
return false;
|
|
||||||
} else if (pat2[idx2] === "\\%") {
|
|
||||||
if (pat1[idx1] !== "\\%") return false;
|
|
||||||
} else if (pat1[idx1] === "\\_") {
|
|
||||||
// Ignore
|
|
||||||
} else if (pat2[idx2] === "\\_") {
|
|
||||||
return false;
|
|
||||||
} else if (pat2[idx2] !== pat1[idx1]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
++idx1;
|
|
||||||
++idx2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx1 === pat1.length && idx2 === pat2.length) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
"use strict";
|
import * as parsimmon from "parsimmon";
|
||||||
|
import { Expression } from "../types";
|
||||||
import parsimmon from "parsimmon";
|
|
||||||
|
|
||||||
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
|
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
|
||||||
function interpretEscapes(str) {
|
function interpretEscapes(str): string {
|
||||||
const escapes = {
|
const escapes = {
|
||||||
b: "\b",
|
b: "\b",
|
||||||
f: "\f",
|
f: "\f",
|
||||||
@@ -22,7 +21,7 @@ function interpretEscapes(str) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function map(exp, callback) {
|
export function map(exp, callback): Expression {
|
||||||
if (!Array.isArray(exp)) return callback(exp);
|
if (!Array.isArray(exp)) return callback(exp);
|
||||||
|
|
||||||
let clone;
|
let clone;
|
||||||
@@ -37,7 +36,7 @@ export function map(exp, callback) {
|
|||||||
return callback(clone || exp);
|
return callback(clone || exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
function binaryLeft(operatorsParser, nextParser) {
|
function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<{}> {
|
||||||
return parsimmon.seqMap(
|
return parsimmon.seqMap(
|
||||||
nextParser,
|
nextParser,
|
||||||
parsimmon.seq(operatorsParser, nextParser).many(),
|
parsimmon.seq(operatorsParser, nextParser).many(),
|
||||||
@@ -257,7 +256,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
Expression: function(r) {
|
Expression: function(r) {
|
||||||
function unary(operatorsParser, nextParser) {
|
function unary(operatorsParser, nextParser): parsimmon.Parser<{}> {
|
||||||
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
|
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,12 +270,12 @@ const lang = parsimmon.createLanguage({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parse(str) {
|
export function parse(str): Expression {
|
||||||
if (!str) return null;
|
if (!str) return null;
|
||||||
return lang.Expression.tryParse(str);
|
return lang.Expression.tryParse(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringify(exp, level = 0) {
|
export function stringify(exp, level = 0): string {
|
||||||
if (!Array.isArray(exp)) return JSON.stringify(exp);
|
if (!Array.isArray(exp)) return JSON.stringify(exp);
|
||||||
|
|
||||||
const opLevels = {
|
const opLevels = {
|
||||||
@@ -302,7 +301,7 @@ export function stringify(exp, level = 0) {
|
|||||||
|
|
||||||
const op = exp[0].toUpperCase();
|
const op = exp[0].toUpperCase();
|
||||||
|
|
||||||
function wrap(e) {
|
function wrap(e): string {
|
||||||
if (opLevels[op] <= level) return `(${e})`;
|
if (opLevels[op] <= level) return `(${e})`;
|
||||||
else return e;
|
else return e;
|
||||||
}
|
}
|
||||||
@@ -360,7 +359,7 @@ export function stringify(exp, level = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseLikePattern(pat, esc) {
|
export function parseLikePattern(pat, esc): 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) {
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { map, parseLikePattern } from "./expression-parser";
|
import { map, parseLikePattern } from "./expression-parser";
|
||||||
import { booleanCnf } from "./expression-cnf";
|
import { booleanCnf } from "./expression-cnf";
|
||||||
import { naiveDpll } from "./sat-solver";
|
import { naiveDpll } from "./sat-solver";
|
||||||
|
import { Expression } from "../types";
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
const regExpCache = new WeakMap();
|
const regExpCache = new WeakMap();
|
||||||
|
|
||||||
const REDUCE_SKIP = {};
|
const REDUCE_SKIP = {};
|
||||||
function reduce(exp, callback) {
|
function reduce(exp, callback): Expression {
|
||||||
let loop = true;
|
let loop = true;
|
||||||
while (loop) {
|
while (loop) {
|
||||||
loop = false;
|
loop = false;
|
||||||
@@ -26,8 +25,45 @@ function reduce(exp, callback) {
|
|||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evaluate(exp, obj, now, cb) {
|
export function likePatternToRegExp(pat, esc = "", flags = ""): RegExp {
|
||||||
function getRegExp(pat, esc) {
|
const convChars = {
|
||||||
|
"-": "\\-",
|
||||||
|
"/": "\\/",
|
||||||
|
"\\": "\\/",
|
||||||
|
"^": "\\^",
|
||||||
|
$: "\\$",
|
||||||
|
"*": "\\*",
|
||||||
|
"+": "\\+",
|
||||||
|
"?": "\\?",
|
||||||
|
".": "\\.",
|
||||||
|
"(": "\\(",
|
||||||
|
")": "\\)",
|
||||||
|
"|": "\\|",
|
||||||
|
"[": "\\[",
|
||||||
|
"]": "\\]",
|
||||||
|
"{": "\\{",
|
||||||
|
"}": "\\}",
|
||||||
|
"\\%": ".*",
|
||||||
|
"\\_": "."
|
||||||
|
};
|
||||||
|
let chars = parseLikePattern(pat, esc);
|
||||||
|
if (!chars.length) return new RegExp("^$", flags);
|
||||||
|
chars = chars.map(c => convChars[c] || c);
|
||||||
|
chars[0] = chars[0] === ".*" ? "" : "^" + chars[0];
|
||||||
|
const l = chars.length - 1;
|
||||||
|
chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$";
|
||||||
|
return new RegExp(chars.join(""), flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluate(
|
||||||
|
exp,
|
||||||
|
obj,
|
||||||
|
now: number,
|
||||||
|
cb?: Function
|
||||||
|
): string | number | boolean | null;
|
||||||
|
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression;
|
||||||
|
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression {
|
||||||
|
function getRegExp(pat, esc): RegExp {
|
||||||
const k = `${esc || ""}:${pat}`;
|
const k = `${esc || ""}:${pat}`;
|
||||||
let c = regExpCache.get(exp);
|
let c = regExpCache.get(exp);
|
||||||
if (!c) regExpCache.set(exp, (c = {}));
|
if (!c) regExpCache.set(exp, (c = {}));
|
||||||
@@ -162,11 +198,11 @@ export function evaluate(exp, obj, now, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function and(exp1, exp2) {
|
export function and(exp1, exp2): Expression {
|
||||||
if (!isArray(exp1)) return exp1 ? exp2 : exp1;
|
if (!isArray(exp1)) return exp1 ? exp2 : exp1;
|
||||||
if (!isArray(exp2)) return exp2 ? exp1 : exp2;
|
if (!isArray(exp2)) return exp2 ? exp1 : exp2;
|
||||||
|
|
||||||
let res = ["AND"];
|
let res: Expression = ["AND"];
|
||||||
|
|
||||||
if (exp1[0] === "AND") res = res.concat(exp1.slice(1));
|
if (exp1[0] === "AND") res = res.concat(exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
@@ -177,11 +213,11 @@ export function and(exp1, exp2) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function or(exp1, exp2) {
|
export function or(exp1, exp2): Expression {
|
||||||
if (!isArray(exp1)) return exp1 ? exp1 : exp2;
|
if (!isArray(exp1)) return exp1 ? exp1 : exp2;
|
||||||
if (!isArray(exp2)) return exp2 ? exp2 : exp1;
|
if (!isArray(exp2)) return exp2 ? exp2 : exp1;
|
||||||
|
|
||||||
let res = ["OR"];
|
let res: Expression = ["OR"];
|
||||||
|
|
||||||
if (exp1[0] === "OR") res = res.concat(exp1.slice(1));
|
if (exp1[0] === "OR") res = res.concat(exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
@@ -192,49 +228,19 @@ export function or(exp1, exp2) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function not(exp) {
|
export function not(exp): 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) {
|
export function subset(exp1, exp2): 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 likePatternToRegExp(pat, esc = "", flags = "") {
|
export function extractParams(exp): string[] {
|
||||||
const convChars = {
|
|
||||||
"-": "\\-",
|
|
||||||
"/": "\\/",
|
|
||||||
"\\": "\\/",
|
|
||||||
"^": "\\^",
|
|
||||||
$: "\\$",
|
|
||||||
"*": "\\*",
|
|
||||||
"+": "\\+",
|
|
||||||
"?": "\\?",
|
|
||||||
".": "\\.",
|
|
||||||
"(": "\\(",
|
|
||||||
")": "\\)",
|
|
||||||
"|": "\\|",
|
|
||||||
"[": "\\[",
|
|
||||||
"]": "\\]",
|
|
||||||
"{": "\\{",
|
|
||||||
"}": "\\}",
|
|
||||||
"\\%": ".*",
|
|
||||||
"\\_": "."
|
|
||||||
};
|
|
||||||
let chars = parseLikePattern(pat, esc);
|
|
||||||
if (!chars.length) return new RegExp("^$", flags);
|
|
||||||
chars = chars.map(c => convChars[c] || c);
|
|
||||||
chars[0] = chars[0] === ".*" ? "" : "^" + chars[0];
|
|
||||||
const l = chars.length - 1;
|
|
||||||
chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$";
|
|
||||||
return new RegExp(chars.join(""), flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractParams(exp) {
|
|
||||||
const params = new Set();
|
const params = new Set();
|
||||||
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]);
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
let cache1 = new Map();
|
let cache1 = new Map();
|
||||||
let cache2 = new Map();
|
let cache2 = new Map();
|
||||||
const keys = new WeakMap();
|
const keys = new WeakMap();
|
||||||
|
|
||||||
function getKey(obj) {
|
function getKey(obj): string {
|
||||||
if (obj === null) return "null";
|
if (obj === null) return "null";
|
||||||
else if (obj === undefined) return "undefined";
|
else if (obj === undefined) return "undefined";
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ function getKey(obj) {
|
|||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function memoize(func) {
|
export default function memoize(func): Function {
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
const key = JSON.stringify(args.map(getKey)) + getKey(func);
|
const key = JSON.stringify(args.map(getKey)) + getKey(func);
|
||||||
|
|
||||||
@@ -14,9 +14,10 @@
|
|||||||
* The code is very similar to the pure table solver, except we use a
|
* The code is very similar to the pure table solver, except we use a
|
||||||
* unit-propagating, derived-varlist-returing satisfiableAt.
|
* unit-propagating, derived-varlist-returing satisfiableAt.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function satisfiableAt(clauses, varVals, varnr, val) {
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
|
|
||||||
|
function satisfiableAt(clauses, varVals, varnr, val): boolean {
|
||||||
if (varnr !== 0) varVals[varnr] = val;
|
if (varnr !== 0) varVals[varnr] = val;
|
||||||
|
|
||||||
const propRes = unitPropagate(clauses, varVals);
|
const propRes = unitPropagate(clauses, varVals);
|
||||||
@@ -87,7 +88,7 @@ function satisfiableAt(clauses, varVals, varnr, val) {
|
|||||||
* undetermined. The values of variables on the list will be restored (set to
|
* undetermined. The values of variables on the list will be restored (set to
|
||||||
* 0) during backtracking.
|
* 0) during backtracking.
|
||||||
*/
|
*/
|
||||||
function unitPropagate(clauses, varVals) {
|
function unitPropagate(clauses, varVals): number[] | 1 | -1 {
|
||||||
// all vars with a value derived in the forthcoming main loop
|
// all vars with a value derived in the forthcoming main loop
|
||||||
const derivedVars = [];
|
const derivedVars = [];
|
||||||
|
|
||||||
@@ -167,7 +168,7 @@ function unitPropagate(clauses, varVals) {
|
|||||||
return derivedVars;
|
return derivedVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function naiveDpll(clauses, maxvarnr) {
|
export function naiveDpll(clauses, maxvarnr): 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);
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default function pass() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function pass(): number {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function test(resourceType, resource) {
|
export default function test(resourceType, resource): number {
|
||||||
if (resourceType === "devices") {
|
if (resourceType === "devices") {
|
||||||
if (resource["Tags.test"] && resource["Tags.test"].value[0]) return 1;
|
if (resource["Tags.test"] && resource["Tags.test"].value[0]) return 1;
|
||||||
} else if (resourceType === "presets") {
|
} else if (resourceType === "presets") {
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
@@ -90,9 +89,9 @@ const options = {
|
|||||||
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 }
|
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 }
|
||||||
};
|
};
|
||||||
|
|
||||||
const allConfig = {};
|
const allConfig: { [name: string]: string | number } = {};
|
||||||
|
|
||||||
function setConfig(name, value, commandLineArgument) {
|
function setConfig(name, value, commandLineArgument = false): boolean {
|
||||||
if (allConfig[name] != null) return true;
|
if (allConfig[name] != null) return true;
|
||||||
|
|
||||||
// For compatibility with v1.0
|
// For compatibility with v1.0
|
||||||
@@ -119,7 +118,7 @@ function setConfig(name, value, commandLineArgument) {
|
|||||||
|
|
||||||
if (name === "FS_IP" || name === "fs-ip") setConfig("FS_HOSTNAME", value);
|
if (name === "FS_IP" || name === "fs-ip") setConfig("FS_HOSTNAME", value);
|
||||||
|
|
||||||
function cast(val, type) {
|
function cast(val, type): string | number | boolean {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "int":
|
case "int":
|
||||||
return Number(val);
|
return Number(val);
|
||||||
@@ -182,7 +181,9 @@ setConfig("CONFIG_DIR", options["CONFIG_DIR"]["default"]);
|
|||||||
|
|
||||||
// Configuration file
|
// Configuration file
|
||||||
const configFile = JSON.parse(
|
const configFile = JSON.parse(
|
||||||
readFileSync(resolve(allConfig.CONFIG_DIR, "config.json"))
|
readFileSync(
|
||||||
|
resolve(allConfig.CONFIG_DIR as string, "config.json")
|
||||||
|
).toString()
|
||||||
);
|
);
|
||||||
for (const [k, v] of Object.entries(configFile)) {
|
for (const [k, v] of Object.entries(configFile)) {
|
||||||
if (!setConfig(k, v))
|
if (!setConfig(k, v))
|
||||||
@@ -194,7 +195,10 @@ for (const [k, v] of Object.entries(configFile)) {
|
|||||||
for (const [k, v] of Object.entries(options))
|
for (const [k, v] of Object.entries(options))
|
||||||
if (v["default"] != null) setConfig(k, v["default"]);
|
if (v["default"] != null) setConfig(k, v["default"]);
|
||||||
|
|
||||||
export function get(optionName, deviceId) {
|
export function get(
|
||||||
|
optionName: string,
|
||||||
|
deviceId?: string
|
||||||
|
): string | number | boolean {
|
||||||
if (!deviceId) return allConfig[optionName];
|
if (!deviceId) return allConfig[optionName];
|
||||||
|
|
||||||
optionName = `${optionName}-${deviceId}`;
|
optionName = `${optionName}-${deviceId}`;
|
||||||
@@ -222,7 +226,7 @@ export function get(optionName, deviceId) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefault(optionName) {
|
export function getDefault(optionName): string | number | boolean {
|
||||||
const option = options[optionName];
|
const option = options[optionName];
|
||||||
if (!option) return null;
|
if (!option) return null;
|
||||||
|
|
||||||
@@ -235,7 +239,7 @@ export function getDefault(optionName) {
|
|||||||
export let auth;
|
export let auth;
|
||||||
// Load authentication scripts
|
// Load authentication scripts
|
||||||
try {
|
try {
|
||||||
auth = require(resolve(allConfig.CONFIG_DIR, "auth.js"));
|
auth = require(resolve(allConfig.CONFIG_DIR as string, "auth.js"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// No auth.js exists
|
// No auth.js exists
|
||||||
}
|
}
|
||||||
+94
-40
@@ -16,11 +16,11 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import zlib from "zlib";
|
import * as zlib from "zlib";
|
||||||
import crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { Socket } from "net";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import * as soap from "./soap";
|
import * as soap from "./soap";
|
||||||
@@ -32,9 +32,19 @@ import * as localCache from "./local-cache";
|
|||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import * as scheduling from "./scheduling";
|
import * as scheduling from "./scheduling";
|
||||||
|
import {
|
||||||
|
SessionContext,
|
||||||
|
ParamPath,
|
||||||
|
AcsRequest,
|
||||||
|
SetAcsRequest,
|
||||||
|
SessionFault,
|
||||||
|
Operation
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
const MAX_CYCLES = 4;
|
const MAX_CYCLES = 4;
|
||||||
const MAX_CONCURRENT_REQUESTS = config.get("MAX_CONCURRENT_REQUESTS");
|
const MAX_CONCURRENT_REQUESTS = +config.get("MAX_CONCURRENT_REQUESTS");
|
||||||
|
|
||||||
|
const currentSessions = new WeakMap<Socket, SessionContext>();
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
concurrentRequests: 0,
|
concurrentRequests: 0,
|
||||||
@@ -43,7 +53,7 @@ const stats = {
|
|||||||
initiatedSessions: 0
|
initiatedSessions: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
function throwError(err, httpResponse) {
|
function throwError(err, httpResponse?): never {
|
||||||
if (httpResponse) {
|
if (httpResponse) {
|
||||||
currentSessions.delete(httpResponse.connection);
|
currentSessions.delete(httpResponse.connection);
|
||||||
httpResponse.writeHead(500, { Connection: "close" });
|
httpResponse.writeHead(500, { Connection: "close" });
|
||||||
@@ -53,7 +63,11 @@ function throwError(err, httpResponse) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeResponse(sessionContext, res, close) {
|
function writeResponse(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
res,
|
||||||
|
close = false
|
||||||
|
): void {
|
||||||
// Close connection after last request in session
|
// Close connection after last request in session
|
||||||
if (close) res.headers["Connection"] = "close";
|
if (close) res.headers["Connection"] = "close";
|
||||||
|
|
||||||
@@ -69,7 +83,7 @@ function writeResponse(sessionContext, res, close) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function finish() {
|
function finish(): void {
|
||||||
stats.concurrentRequests -= 1;
|
stats.concurrentRequests -= 1;
|
||||||
if (sessionContext.httpRequest.connection.destroyed) {
|
if (sessionContext.httpRequest.connection.destroyed) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
@@ -130,7 +144,9 @@ function writeResponse(sessionContext, res, close) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordFault(sessionContext, fault, provisions, channels) {
|
function recordFault(sessionContext, fault, provisions, channels): void;
|
||||||
|
function recordFault(sessionContext, fault): void;
|
||||||
|
function recordFault(sessionContext, fault, provisions?, channels?): void {
|
||||||
if (!provisions) {
|
if (!provisions) {
|
||||||
provisions = sessionContext.provisions;
|
provisions = sessionContext.provisions;
|
||||||
channels = sessionContext.channels;
|
channels = sessionContext.channels;
|
||||||
@@ -185,7 +201,7 @@ function recordFault(sessionContext, fault, provisions, channels) {
|
|||||||
session.clearProvisions(sessionContext);
|
session.clearProvisions(sessionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inform(sessionContext, rpc) {
|
function inform(sessionContext, rpc): void {
|
||||||
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) => {
|
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) => {
|
||||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||||
|
|
||||||
@@ -213,7 +229,7 @@ function inform(sessionContext, rpc) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function transferComplete(sessionContext, rpc) {
|
function transferComplete(sessionContext, rpc): void {
|
||||||
session.transferComplete(
|
session.transferComplete(
|
||||||
sessionContext,
|
sessionContext,
|
||||||
rpc.cpeRequest,
|
rpc.cpeRequest,
|
||||||
@@ -250,7 +266,7 @@ function transferComplete(sessionContext, rpc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append providions and remove duplicates
|
// Append providions and remove duplicates
|
||||||
function appendProvisions(original, toAppend) {
|
function appendProvisions(original, toAppend): boolean {
|
||||||
let modified = false;
|
let modified = false;
|
||||||
const stringified = new WeakMap();
|
const stringified = new WeakMap();
|
||||||
|
|
||||||
@@ -281,7 +297,7 @@ function appendProvisions(original, toAppend) {
|
|||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPresets(sessionContext) {
|
function applyPresets(sessionContext: SessionContext): void {
|
||||||
const deviceData = sessionContext.deviceData;
|
const deviceData = sessionContext.deviceData;
|
||||||
const presets = localCache.getPresets(sessionContext.cacheSnapshot);
|
const presets = localCache.getPresets(sessionContext.cacheSnapshot);
|
||||||
|
|
||||||
@@ -289,7 +305,7 @@ function applyPresets(sessionContext) {
|
|||||||
const blackList = {};
|
const blackList = {};
|
||||||
let whiteList = null;
|
let whiteList = null;
|
||||||
let whiteListProvisions = null;
|
let whiteListProvisions = null;
|
||||||
const RETRY_DELAY = localCache.getConfig(
|
const RETRY_DELAY = +localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.retryDelay",
|
"cwmp.retryDelay",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
@@ -318,13 +334,17 @@ function applyPresets(sessionContext) {
|
|||||||
deviceData.attributes.revision = 1;
|
deviceData.attributes.revision = 1;
|
||||||
|
|
||||||
const deviceEvents = {};
|
const deviceEvents = {};
|
||||||
for (const p of deviceData.paths.find(["Events", "*"], false, true)) {
|
for (const p of deviceData.paths.find(
|
||||||
|
["Events", "*"] as ParamPath,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)) {
|
||||||
const attrs = deviceData.attributes.get(p);
|
const attrs = deviceData.attributes.get(p);
|
||||||
if (attrs && attrs.value && attrs.value[1][0] >= sessionContext.timestamp)
|
if (attrs && attrs.value && attrs.value[1][0] >= sessionContext.timestamp)
|
||||||
deviceEvents[p[1]] = true;
|
deviceEvents[p[1]] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = {};
|
const parameters: { [name: string]: ParamPath } = {};
|
||||||
const filteredPresets = [];
|
const filteredPresets = [];
|
||||||
|
|
||||||
for (const preset of presets) {
|
for (const preset of presets) {
|
||||||
@@ -478,7 +498,7 @@ function applyPresets(sessionContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextRpc(sessionContext) {
|
function nextRpc(sessionContext: SessionContext): void {
|
||||||
session.rpcRequest(sessionContext, null, (err, fault, id, acsRequest) => {
|
session.rpcRequest(sessionContext, null, (err, fault, id, acsRequest) => {
|
||||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||||
|
|
||||||
@@ -604,7 +624,7 @@ function nextRpc(sessionContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function endSession(sessionContext, callback) {
|
function endSession(sessionContext: SessionContext, callback): void {
|
||||||
let saveCache = sessionContext.cacheUntil != null;
|
let saveCache = sessionContext.cacheUntil != null;
|
||||||
let counter = 3;
|
let counter = 3;
|
||||||
|
|
||||||
@@ -714,24 +734,29 @@ function endSession(sessionContext, callback) {
|
|||||||
if ((counter -= 2) === 1) callback(null, sessionContext.new);
|
if ((counter -= 2) === 1) callback(null, sessionContext.new);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendAcsRequest(sessionContext, id, acsRequest) {
|
function sendAcsRequest(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
id?: string,
|
||||||
|
acsRequest?: AcsRequest
|
||||||
|
): void {
|
||||||
if (!acsRequest)
|
if (!acsRequest)
|
||||||
return void writeResponse(sessionContext, soap.response(null), true);
|
return void writeResponse(sessionContext, soap.response(null), true);
|
||||||
|
|
||||||
if (acsRequest.name === "Download") {
|
if (acsRequest.name === "Download") {
|
||||||
acsRequest.fileSize = 0;
|
const downloadRequest = acsRequest as SetAcsRequest;
|
||||||
if (!acsRequest.url) {
|
downloadRequest.fileSize = 0;
|
||||||
|
if (!downloadRequest.url) {
|
||||||
const FS_PORT = config.get("FS_PORT");
|
const FS_PORT = config.get("FS_PORT");
|
||||||
const FS_HOSTNAME = config.get("FS_HOSTNAME");
|
const FS_HOSTNAME = config.get("FS_HOSTNAME");
|
||||||
const FS_SSL = config.get("FS_SSL");
|
const FS_SSL = config.get("FS_SSL");
|
||||||
acsRequest.url = FS_SSL ? "https://" : "http://";
|
downloadRequest.url = FS_SSL ? "https://" : "http://";
|
||||||
acsRequest.url += FS_HOSTNAME;
|
downloadRequest.url += FS_HOSTNAME;
|
||||||
if (FS_PORT !== 80) acsRequest.url += ":" + FS_PORT;
|
if (FS_PORT !== 80) downloadRequest.url += ":" + FS_PORT;
|
||||||
acsRequest.url += "/" + encodeURI(acsRequest.fileName);
|
downloadRequest.url += "/" + encodeURI(downloadRequest.fileName);
|
||||||
|
|
||||||
const files = localCache.getFiles(sessionContext.cacheSnapshot);
|
const files = localCache.getFiles(sessionContext.cacheSnapshot);
|
||||||
if (files[acsRequest.fileName])
|
if (files[downloadRequest.fileName])
|
||||||
acsRequest.fileSize = files[acsRequest.fileName].length;
|
downloadRequest.fileSize = files[downloadRequest.fileName].length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,7 +776,11 @@ function sendAcsRequest(sessionContext, id, acsRequest) {
|
|||||||
writeResponse(sessionContext, res);
|
writeResponse(sessionContext, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSession(connection, sessionId, callback) {
|
function getSession(
|
||||||
|
connection,
|
||||||
|
sessionId,
|
||||||
|
callback: (err?: Error, sessionContext?: SessionContext) => void
|
||||||
|
): void {
|
||||||
const sessionContext = currentSessions.get(connection);
|
const sessionContext = currentSessions.get(connection);
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
currentSessions.delete(connection);
|
currentSessions.delete(connection);
|
||||||
@@ -768,10 +797,8 @@ function getSession(connection, sessionId, callback) {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSessions = new WeakMap();
|
|
||||||
|
|
||||||
// When socket closes, store active sessions in cache
|
// When socket closes, store active sessions in cache
|
||||||
export function onConnection(socket) {
|
export function onConnection(socket): void {
|
||||||
// The property remoteAddress may be undefined after the connection is
|
// The property remoteAddress may be undefined after the connection is
|
||||||
// closed, unless we read it at least once (caching?)
|
// closed, unless we read it at least once (caching?)
|
||||||
socket.remoteAddress;
|
socket.remoteAddress;
|
||||||
@@ -840,7 +867,29 @@ setInterval(() => {
|
|||||||
stats.initiatedSessions = 0;
|
stats.initiatedSessions = 0;
|
||||||
}, 10000).unref();
|
}, 10000).unref();
|
||||||
|
|
||||||
function getDueTasksAndFaultsAndOperations(deviceId, timestamp, callback) {
|
interface GetDueTasksAndFaultsAndOperationsCallback {
|
||||||
|
(
|
||||||
|
err: null,
|
||||||
|
tasks: any[],
|
||||||
|
faults: { [channel: string]: SessionFault },
|
||||||
|
operations: { [commandKey: string]: Operation },
|
||||||
|
ttl: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
(
|
||||||
|
err: Error,
|
||||||
|
tasks?: null,
|
||||||
|
faults?: null,
|
||||||
|
operations?: null,
|
||||||
|
ttl?: null
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDueTasksAndFaultsAndOperations(
|
||||||
|
deviceId,
|
||||||
|
timestamp,
|
||||||
|
callback: GetDueTasksAndFaultsAndOperationsCallback
|
||||||
|
): void {
|
||||||
cache.get(`${deviceId}_tasks_faults_operations`, (err, res) => {
|
cache.get(`${deviceId}_tasks_faults_operations`, (err, res) => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
|
|
||||||
@@ -850,7 +899,8 @@ function getDueTasksAndFaultsAndOperations(deviceId, timestamp, callback) {
|
|||||||
null,
|
null,
|
||||||
res.tasks || [],
|
res.tasks || [],
|
||||||
res.faults || {},
|
res.faults || {},
|
||||||
res.operations || {}
|
res.operations || {},
|
||||||
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,8 +949,12 @@ function cacheDueTasksAndFaultsAndOperations(
|
|||||||
operations,
|
operations,
|
||||||
cacheUntil,
|
cacheUntil,
|
||||||
callback
|
callback
|
||||||
) {
|
): void {
|
||||||
const v = {};
|
const v = {
|
||||||
|
tasks: null,
|
||||||
|
faults: 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;
|
||||||
if (Object.keys(operations).length) v.operations = operations;
|
if (Object.keys(operations).length) v.operations = operations;
|
||||||
@@ -917,7 +971,7 @@ function cacheDueTasksAndFaultsAndOperations(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRequest(sessionContext, rpc) {
|
function processRequest(sessionContext, rpc): void {
|
||||||
if (rpc.cpeRequest) {
|
if (rpc.cpeRequest) {
|
||||||
if (rpc.cpeRequest.name === "Inform") {
|
if (rpc.cpeRequest.name === "Inform") {
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
@@ -996,7 +1050,7 @@ function processRequest(sessionContext, rpc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listener(httpRequest, httpResponse) {
|
export function listener(httpRequest, httpResponse): void {
|
||||||
stats.totalRequests += 1;
|
stats.totalRequests += 1;
|
||||||
|
|
||||||
if (httpRequest.method !== "POST") {
|
if (httpRequest.method !== "POST") {
|
||||||
@@ -1064,7 +1118,7 @@ export function listener(httpRequest, httpResponse) {
|
|||||||
offset += chunk.length;
|
offset += chunk.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsedRpc(sessionContext, rpc, parseWarnings) {
|
function parsedRpc(sessionContext, rpc, parseWarnings): void {
|
||||||
for (const w of parseWarnings) {
|
for (const w of parseWarnings) {
|
||||||
w.sessionContext = sessionContext;
|
w.sessionContext = sessionContext;
|
||||||
w.rpc = rpc;
|
w.rpc = rpc;
|
||||||
@@ -1143,7 +1197,7 @@ export function listener(httpRequest, httpResponse) {
|
|||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
if (
|
if (
|
||||||
(rpc.cpeRequest && rpc.cpeRequest.name === "Inform") ||
|
(rpc.cpeRequest && rpc.cpeRequest.name === "Inform") ||
|
||||||
!sessionContext.rpcRequest ^ !(rpc.cpeResponse || rpc.cpeFault)
|
!sessionContext.rpcRequest !== !(rpc.cpeResponse || rpc.cpeFault)
|
||||||
) {
|
) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Bad session state",
|
message: "Bad session state",
|
||||||
@@ -1201,7 +1255,7 @@ export function listener(httpRequest, httpResponse) {
|
|||||||
|
|
||||||
_sessionContext.cacheSnapshot = cacheSnapshot;
|
_sessionContext.cacheSnapshot = cacheSnapshot;
|
||||||
_sessionContext.configContext = configContext;
|
_sessionContext.configContext = configContext;
|
||||||
_sessionContext.debug = localCache.getConfig(
|
_sessionContext.debug = !!localCache.getConfig(
|
||||||
cacheSnapshot,
|
cacheSnapshot,
|
||||||
"cwmp.debug",
|
"cwmp.debug",
|
||||||
configContext
|
configContext
|
||||||
+34
-33
@@ -16,7 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { MongoClient, ObjectID } from "mongodb";
|
import { MongoClient, ObjectID } from "mongodb";
|
||||||
import { get } from "./config";
|
import { get } from "./config";
|
||||||
@@ -36,9 +35,9 @@ export let tasksCollection,
|
|||||||
|
|
||||||
export let client;
|
export let client;
|
||||||
|
|
||||||
export function connect(callback) {
|
export function connect(callback): void {
|
||||||
MongoClient.connect(
|
MongoClient.connect(
|
||||||
get("MONGODB_CONNECTION_URL"),
|
"" + get("MONGODB_CONNECTION_URL"),
|
||||||
{ useNewUrlParser: true },
|
{ useNewUrlParser: true },
|
||||||
(err, _client) => {
|
(err, _client) => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
@@ -63,13 +62,13 @@ export function connect(callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disconnect() {
|
export function disconnect(): void {
|
||||||
if (client) client.close();
|
if (client) client.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: {}): {} {
|
||||||
if (obj[""]) return { "": obj[""] };
|
if (obj[""]) return { "": obj[""] };
|
||||||
|
|
||||||
const keys = Object.keys(obj).sort();
|
const keys = Object.keys(obj).sort();
|
||||||
@@ -88,14 +87,14 @@ function optimizeProjection(obj) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchDevice(id, timestamp, patterns, callback) {
|
export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||||
const MAX_DEPTH = get("MAX_DEPTH", id);
|
const MAX_DEPTH = +get("MAX_DEPTH", id);
|
||||||
|
|
||||||
if (!patterns || !patterns.length) patterns = [[[], (1 << MAX_DEPTH) - 1]];
|
if (!patterns || !patterns.length) patterns = [[[], (1 << MAX_DEPTH) - 1]];
|
||||||
|
|
||||||
let projection = {};
|
let projection = {};
|
||||||
const projectionTree = {};
|
const projectionTree = {};
|
||||||
function func(path, pats, projTree) {
|
function func(path, pats, projTree): void {
|
||||||
const children = {};
|
const children = {};
|
||||||
for (const pat of pats) {
|
for (const pat of pats) {
|
||||||
const fragment = pat[0][path.length] || "*";
|
const fragment = pat[0][path.length] || "*";
|
||||||
@@ -232,16 +231,16 @@ export function fetchDevice(id, timestamp, patterns, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {};
|
let options;
|
||||||
if (!projection[""]) {
|
if (!projection[""]) {
|
||||||
if (!Object.keys(projection).length) options.projection = { _id: 1 };
|
if (!Object.keys(projection).length) options = { projection: { _id: 1 } };
|
||||||
else options.projection = projection;
|
else options = { projection: projection };
|
||||||
}
|
}
|
||||||
|
|
||||||
devicesCollection.findOne({ _id: id }, options, (err, device) => {
|
devicesCollection.findOne({ _id: id }, options, (err, device) => {
|
||||||
if (err || !device) return void callback(err);
|
if (err || !device) return void callback(err);
|
||||||
|
|
||||||
function storeParams(obj, path, ts, descendantsFetched, projTree) {
|
function storeParams(obj, path, ts, descendantsFetched, projTree): void {
|
||||||
let thisFetched = false;
|
let thisFetched = false;
|
||||||
if (descendantsFetched) {
|
if (descendantsFetched) {
|
||||||
thisFetched = true;
|
thisFetched = true;
|
||||||
@@ -278,7 +277,11 @@ export function fetchDevice(id, timestamp, patterns, callback) {
|
|||||||
if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true;
|
if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true;
|
||||||
|
|
||||||
if (thisFetched) {
|
if (thisFetched) {
|
||||||
const attrs = {};
|
const attrs: {
|
||||||
|
object?: [number, number];
|
||||||
|
writable?: [number, number];
|
||||||
|
value?: [number, [string | number | boolean, string]];
|
||||||
|
} = {};
|
||||||
let t = obj["_timestamp"] || 1;
|
let t = obj["_timestamp"] || 1;
|
||||||
if (ts > t) t = ts;
|
if (ts > t) t = ts;
|
||||||
|
|
||||||
@@ -392,7 +395,7 @@ export function fetchDevice(id, timestamp, patterns, callback) {
|
|||||||
delete device[k];
|
delete device[k];
|
||||||
break;
|
break;
|
||||||
case "_tags":
|
case "_tags":
|
||||||
if (v.length) {
|
if ((v as string[]).length) {
|
||||||
res.push([
|
res.push([
|
||||||
["Tags"],
|
["Tags"],
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -400,7 +403,7 @@ export function fetchDevice(id, timestamp, patterns, callback) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let t of v) {
|
for (let t of v as string[]) {
|
||||||
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
|
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
|
||||||
res.push([
|
res.push([
|
||||||
["Tags", t],
|
["Tags", t],
|
||||||
@@ -478,7 +481,7 @@ export function saveDevice(
|
|||||||
isNew,
|
isNew,
|
||||||
sessionTimestamp,
|
sessionTimestamp,
|
||||||
callback
|
callback
|
||||||
) {
|
): void {
|
||||||
const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} };
|
const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} };
|
||||||
|
|
||||||
for (const diff of deviceData.timestamps.diff()) {
|
for (const diff of deviceData.timestamps.diff()) {
|
||||||
@@ -501,7 +504,7 @@ export function saveDevice(
|
|||||||
.concat("_timestamp")
|
.concat("_timestamp")
|
||||||
.join(".")
|
.join(".")
|
||||||
] = 1;
|
] = 1;
|
||||||
} else if (diff[2] !== diff[1]) {
|
} else {
|
||||||
const parent = deviceData.paths.get(diff[0].slice(0, -1));
|
const parent = deviceData.paths.get(diff[0].slice(0, -1));
|
||||||
if (parent && (!parent.length || deviceData.attributes.has(parent))) {
|
if (parent && (!parent.length || deviceData.attributes.has(parent))) {
|
||||||
update["$set"][
|
update["$set"][
|
||||||
@@ -515,8 +518,6 @@ export function saveDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const diff of deviceData.attributes.diff()) {
|
for (const diff of deviceData.attributes.diff()) {
|
||||||
if (diff[1] === diff[2]) continue;
|
|
||||||
|
|
||||||
const path = diff[0];
|
const path = diff[0];
|
||||||
const value1 = (((diff[1] || {}).value || [])[1] || [])[0];
|
const value1 = (((diff[1] || {}).value || [])[1] || [])[0];
|
||||||
const value2 = (((diff[2] || {}).value || [])[1] || [])[0];
|
const value2 = (((diff[2] || {}).value || [])[1] || [])[0];
|
||||||
@@ -726,7 +727,7 @@ export function saveDevice(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFaults(deviceId, callback) {
|
export function getFaults(deviceId, callback): void {
|
||||||
faultsCollection
|
faultsCollection
|
||||||
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
||||||
.toArray((err, res) => {
|
.toArray((err, res) => {
|
||||||
@@ -747,7 +748,7 @@ export function getFaults(deviceId, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveFault(deviceId, channel, fault, callback) {
|
export function saveFault(deviceId, channel, fault, callback): void {
|
||||||
const id = `${deviceId}:${channel}`;
|
const id = `${deviceId}:${channel}`;
|
||||||
fault = Object.assign({}, fault);
|
fault = Object.assign({}, fault);
|
||||||
fault._id = id;
|
fault._id = id;
|
||||||
@@ -758,11 +759,11 @@ export function saveFault(deviceId, channel, fault, callback) {
|
|||||||
faultsCollection.replaceOne({ _id: id }, fault, { upsert: true }, callback);
|
faultsCollection.replaceOne({ _id: id }, fault, { upsert: true }, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFault(deviceId, channel, callback) {
|
export function deleteFault(deviceId, channel, callback): void {
|
||||||
faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` }, callback);
|
faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` }, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDueTasks(deviceId, timestamp, callback) {
|
export function getDueTasks(deviceId, timestamp, callback): void {
|
||||||
const cur = tasksCollection.find({ device: deviceId }).sort(["timestamp"]);
|
const cur = tasksCollection.find({ device: deviceId }).sort(["timestamp"]);
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
|
|
||||||
@@ -807,14 +808,14 @@ export function getDueTasks(deviceId, timestamp, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearTasks(deviceId, taskIds, callback) {
|
export function clearTasks(deviceId, taskIds, callback): void {
|
||||||
tasksCollection.deleteMany(
|
tasksCollection.deleteMany(
|
||||||
{ _id: { $in: taskIds.map(id => new ObjectID(id)) } },
|
{ _id: { $in: taskIds.map(id => new ObjectID(id)) } },
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOperations(deviceId, callback) {
|
export function getOperations(deviceId, callback): void {
|
||||||
operationsCollection
|
operationsCollection
|
||||||
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
||||||
.toArray((err, res) => {
|
.toArray((err, res) => {
|
||||||
@@ -835,7 +836,7 @@ export function getOperations(deviceId, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveOperation(deviceId, commandKey, operation, callback) {
|
export function saveOperation(deviceId, commandKey, operation, callback): void {
|
||||||
const id = `${deviceId}:${commandKey}`;
|
const id = `${deviceId}:${commandKey}`;
|
||||||
operation = Object.assign({}, operation);
|
operation = Object.assign({}, operation);
|
||||||
operation._id = id;
|
operation._id = id;
|
||||||
@@ -851,34 +852,34 @@ export function saveOperation(deviceId, commandKey, operation, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteOperation(deviceId, commandKey, callback) {
|
export function deleteOperation(deviceId, commandKey, callback): void {
|
||||||
operationsCollection.deleteOne(
|
operationsCollection.deleteOne(
|
||||||
{ _id: `${deviceId}:${commandKey}` },
|
{ _id: `${deviceId}:${commandKey}` },
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPresets(callback) {
|
export function getPresets(callback): void {
|
||||||
presetsCollection.find().toArray(callback);
|
presetsCollection.find().toArray(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getObjects(callback) {
|
export function getObjects(callback): void {
|
||||||
objectsCollection.find().toArray(callback);
|
objectsCollection.find().toArray(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProvisions(callback) {
|
export function getProvisions(callback): void {
|
||||||
provisionsCollection.find().toArray(callback);
|
provisionsCollection.find().toArray(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVirtualParameters(callback) {
|
export function getVirtualParameters(callback): void {
|
||||||
virtualParametersCollection.find().toArray(callback);
|
virtualParametersCollection.find().toArray(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFiles(callback) {
|
export function getFiles(callback): void {
|
||||||
filesCollection.find().toArray(callback);
|
filesCollection.find().toArray(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig(callback) {
|
export function getConfig(callback): void {
|
||||||
configCollection.find().toArray((err, res) => {
|
configCollection.find().toArray((err, res) => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
|
|
||||||
@@ -16,16 +16,15 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import * as device from "./device";
|
import * as device from "./device";
|
||||||
import * as scheduling from "./scheduling";
|
import * as scheduling from "./scheduling";
|
||||||
|
|
||||||
const MAX_DEPTH = config.get("MAX_DEPTH");
|
const MAX_DEPTH = +config.get("MAX_DEPTH");
|
||||||
|
|
||||||
export function refresh(sessionContext, provision, declarations) {
|
export function refresh(sessionContext, provision, declarations): boolean {
|
||||||
const path = common.parsePath(provision[1]).slice();
|
const path = common.parsePath(provision[1]).slice();
|
||||||
const l = path.length;
|
const l = path.length;
|
||||||
path.length = MAX_DEPTH;
|
path.length = MAX_DEPTH;
|
||||||
@@ -48,7 +47,7 @@ export function refresh(sessionContext, provision, declarations) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function value(sessionContext, provision, declarations) {
|
export function value(sessionContext, provision, declarations): boolean {
|
||||||
const path = common.parsePath(provision[1]);
|
const path = common.parsePath(provision[1]);
|
||||||
declarations.push({
|
declarations.push({
|
||||||
path: path,
|
path: path,
|
||||||
@@ -62,7 +61,7 @@ export function value(sessionContext, provision, declarations) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tag(sessionContext, provision, declarations) {
|
export function tag(sessionContext, provision, declarations): boolean {
|
||||||
const path = ["Tags", provision[1]];
|
const path = ["Tags", provision[1]];
|
||||||
|
|
||||||
declarations.push({
|
declarations.push({
|
||||||
@@ -77,7 +76,7 @@ export function tag(sessionContext, provision, declarations) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reboot(sessionContext, provision, declarations) {
|
export function reboot(sessionContext, provision, declarations): boolean {
|
||||||
declarations.push({
|
declarations.push({
|
||||||
path: ["Reboot"],
|
path: ["Reboot"],
|
||||||
pathGet: 1,
|
pathGet: 1,
|
||||||
@@ -90,7 +89,7 @@ export function reboot(sessionContext, provision, declarations) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reset(sessionContext, provision, declarations) {
|
export function reset(sessionContext, provision, declarations): boolean {
|
||||||
declarations.push({
|
declarations.push({
|
||||||
path: ["FactoryReset"],
|
path: ["FactoryReset"],
|
||||||
pathGet: 1,
|
pathGet: 1,
|
||||||
@@ -103,7 +102,7 @@ export function reset(sessionContext, provision, declarations) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(sessionContext, provision, declarations) {
|
export function download(sessionContext, provision, declarations): boolean {
|
||||||
const alias = [
|
const alias = [
|
||||||
["FileType"],
|
["FileType"],
|
||||||
provision[1] || "",
|
provision[1] || "",
|
||||||
@@ -140,7 +139,7 @@ export function instances(
|
|||||||
declarations,
|
declarations,
|
||||||
startRevision,
|
startRevision,
|
||||||
endRevision
|
endRevision
|
||||||
) {
|
): boolean {
|
||||||
let count = Number(provision[2]);
|
let count = Number(provision[2]);
|
||||||
|
|
||||||
if (Number.isNaN(count)) return true;
|
if (Number.isNaN(count)) return true;
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
import { ParamPath, DeviceData, Declaration, Clear } from "./types";
|
||||||
|
|
||||||
const CHANGE_FLAGS = {
|
const CHANGE_FLAGS = {
|
||||||
object: 2,
|
object: 2,
|
||||||
@@ -24,7 +25,7 @@ const CHANGE_FLAGS = {
|
|||||||
value: 8
|
value: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseBool(v) {
|
function parseBool(v): boolean {
|
||||||
v = "" + v;
|
v = "" + v;
|
||||||
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
||||||
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
||||||
@@ -32,14 +33,16 @@ function parseBool(v) {
|
|||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeParameterValue(parameterValue) {
|
export function sanitizeParameterValue(
|
||||||
|
parameterValue
|
||||||
|
): [string | number | boolean, string?] {
|
||||||
if (parameterValue[0] != null) {
|
if (parameterValue[0] != null) {
|
||||||
switch (parameterValue[1]) {
|
switch (parameterValue[1]) {
|
||||||
case "xsd:boolean":
|
case "xsd:boolean":
|
||||||
if (typeof parameterValue[0] !== "boolean") {
|
if (typeof parameterValue[0] !== "boolean") {
|
||||||
let v = parseBool(parameterValue[0]);
|
const b = parseBool(parameterValue[0]);
|
||||||
if (v == null) v = "" + parameterValue[0];
|
if (b == null) parameterValue[0] = "" + parameterValue[0];
|
||||||
parameterValue[0] = v;
|
else parameterValue[0] = b;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "xsd:string":
|
case "xsd:string":
|
||||||
@@ -52,20 +55,22 @@ export function sanitizeParameterValue(parameterValue) {
|
|||||||
case "xsd:int":
|
case "xsd:int":
|
||||||
case "xsd:unsignedInt":
|
case "xsd:unsignedInt":
|
||||||
if (typeof parameterValue[0] !== "number") {
|
if (typeof parameterValue[0] !== "number") {
|
||||||
let v = parseInt(parameterValue[0]);
|
const i = parseInt(parameterValue[0]);
|
||||||
if (isNaN(v)) v = "" + parameterValue[0];
|
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
|
||||||
parameterValue[0] = v;
|
else parameterValue[0] = i;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "xsd:dateTime":
|
case "xsd:dateTime":
|
||||||
if (typeof parameterValue[0] !== "number") {
|
if (typeof parameterValue[0] !== "number") {
|
||||||
// Don't use parseInt because it reads date string as a number
|
// Don't use parseInt because it reads date string as a number
|
||||||
let v = +parameterValue[0];
|
let i = +parameterValue[0];
|
||||||
if (isNaN(v)) {
|
if (isNaN(i)) {
|
||||||
v = Date.parse(parameterValue[0]);
|
i = Date.parse(parameterValue[0]);
|
||||||
if (isNaN(v)) v = "" + parameterValue[0];
|
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
|
||||||
|
else parameterValue[0] = i;
|
||||||
|
} else {
|
||||||
|
parameterValue[0] = i;
|
||||||
}
|
}
|
||||||
parameterValue[0] = v;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -83,10 +88,10 @@ export function sanitizeParameterValue(parameterValue) {
|
|||||||
return parameterValue;
|
return parameterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAliasDeclarations(path, timestamp) {
|
export function getAliasDeclarations(path, timestamp): Declaration[] {
|
||||||
const decs = [];
|
const decs = [] as Declaration[];
|
||||||
|
|
||||||
function recursive(p, prefix) {
|
function recursive(p, prefix): void {
|
||||||
const pattern = prefix.concat(p);
|
const pattern = prefix.concat(p);
|
||||||
|
|
||||||
for (let i = prefix.length; i < pattern.length; ++i) {
|
for (let i = prefix.length; i < pattern.length; ++i) {
|
||||||
@@ -113,13 +118,17 @@ export function getAliasDeclarations(path, timestamp) {
|
|||||||
return decs;
|
return decs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unpack(deviceData, path, revision) {
|
export function unpack(
|
||||||
let allMatches = [];
|
deviceData,
|
||||||
|
path: ParamPath,
|
||||||
|
revision?: number
|
||||||
|
): ParamPath[] {
|
||||||
|
let allMatches = [] as ParamPath[];
|
||||||
if (!path.alias) {
|
if (!path.alias) {
|
||||||
for (const p of deviceData.paths.find(path, false, true))
|
for (const p of deviceData.paths.find(path, false, true))
|
||||||
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
|
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
|
||||||
} else {
|
} else {
|
||||||
const wildcardPath = path.slice();
|
const wildcardPath = path.slice() as ParamPath;
|
||||||
wildcardPath.wildcard = path.wildcard;
|
wildcardPath.wildcard = path.wildcard;
|
||||||
for (let i = 0; i < wildcardPath.length; ++i) {
|
for (let i = 0; i < wildcardPath.length; ++i) {
|
||||||
if (Array.isArray(wildcardPath[i])) {
|
if (Array.isArray(wildcardPath[i])) {
|
||||||
@@ -134,7 +143,9 @@ export function unpack(deviceData, path, revision) {
|
|||||||
for (let i = path.length - 1; i >= 0; --i) {
|
for (let i = path.length - 1; i >= 0; --i) {
|
||||||
if (Array.isArray(path[i])) {
|
if (Array.isArray(path[i])) {
|
||||||
for (let j = 0; j < path[i].length; j += 2) {
|
for (let j = 0; j < path[i].length; j += 2) {
|
||||||
const p = wildcardPath.slice(0, i + 1).concat(path[i][j]);
|
const p = wildcardPath
|
||||||
|
.slice(0, i + 1)
|
||||||
|
.concat(path[i][j]) as ParamPath;
|
||||||
p.alias =
|
p.alias =
|
||||||
((wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias) <<
|
((wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias) <<
|
||||||
(i + 1);
|
(i + 1);
|
||||||
@@ -143,7 +154,7 @@ export function unpack(deviceData, path, revision) {
|
|||||||
path[i][j].wildcard) <<
|
path[i][j].wildcard) <<
|
||||||
(i + 1);
|
(i + 1);
|
||||||
const unpacked = unpack(deviceData, p, revision);
|
const unpacked = unpack(deviceData, p, revision);
|
||||||
const filtered = [];
|
const filtered = [] as ParamPath[];
|
||||||
for (const up of unpacked) {
|
for (const up of unpacked) {
|
||||||
const attributes = deviceData.attributes.get(up, revision);
|
const attributes = deviceData.attributes.get(up, revision);
|
||||||
if (
|
if (
|
||||||
@@ -161,7 +172,7 @@ export function unpack(deviceData, path, revision) {
|
|||||||
|
|
||||||
if (k < 0) {
|
if (k < 0) {
|
||||||
filtered.push(allMatches[m]);
|
filtered.push(allMatches[m]);
|
||||||
allMatches[m] = [];
|
allMatches[m] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,21 +183,19 @@ export function unpack(deviceData, path, revision) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allMatches.sort((a, b) => {
|
allMatches.sort((p1, p2) => {
|
||||||
for (let i = 0; i < a.length; ++i) {
|
for (let i = 0; i < p1.length; ++i) {
|
||||||
if (a === b) continue;
|
const a = p1[i];
|
||||||
|
const b = p2[i];
|
||||||
|
if (a !== b) {
|
||||||
|
// Use numeric sorting for numbers
|
||||||
|
const ia = parseInt(a);
|
||||||
|
const ib = parseInt(b);
|
||||||
|
|
||||||
// Use numeric sorting for numbers
|
if (ia === +a && ib === +b) return ia - ib;
|
||||||
const ia = parseInt(a);
|
else if (a < b) return -1;
|
||||||
const ib = parseInt(b);
|
else return 1;
|
||||||
|
|
||||||
if (ia.toString() === a && ib.toString() === b) {
|
|
||||||
a = ia;
|
|
||||||
b = ib;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a < b) return -1;
|
|
||||||
else return 1;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
@@ -200,7 +209,7 @@ export function clear(
|
|||||||
timestamp,
|
timestamp,
|
||||||
attributes,
|
attributes,
|
||||||
changeFlags = 0
|
changeFlags = 0
|
||||||
) {
|
): void {
|
||||||
const changeTrackers = {};
|
const changeTrackers = {};
|
||||||
|
|
||||||
timestamp = timestamp || 0;
|
timestamp = timestamp || 0;
|
||||||
@@ -259,7 +268,7 @@ export function clear(
|
|||||||
if (changeTrackers[k] & changeFlags) deviceData.changes.add(k);
|
if (changeTrackers[k] & changeFlags) deviceData.changes.add(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
function compareEquality(a, b) {
|
function compareEquality(a, b): boolean {
|
||||||
const t = typeof a;
|
const t = typeof a;
|
||||||
if (
|
if (
|
||||||
a === null ||
|
a === null ||
|
||||||
@@ -274,7 +283,13 @@ function compareEquality(a, b) {
|
|||||||
return JSON.stringify(a) === JSON.stringify(b);
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function set(deviceData, path, timestamp, attributes, toClear) {
|
export function set(
|
||||||
|
deviceData: DeviceData,
|
||||||
|
path: ParamPath,
|
||||||
|
timestamp,
|
||||||
|
attributes,
|
||||||
|
toClear?: Clear[]
|
||||||
|
): Clear[] {
|
||||||
path = deviceData.paths.add(path);
|
path = deviceData.paths.add(path);
|
||||||
|
|
||||||
const currentTimestamp = deviceData.timestamps.get(path);
|
const currentTimestamp = deviceData.timestamps.get(path);
|
||||||
@@ -330,7 +345,7 @@ export function set(deviceData, path, timestamp, attributes, toClear) {
|
|||||||
if (path.length > 1) {
|
if (path.length > 1) {
|
||||||
toClear = set(
|
toClear = set(
|
||||||
deviceData,
|
deviceData,
|
||||||
path.slice(0, path.length - 1),
|
path.slice(0, path.length - 1) as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1] },
|
{ object: [timestamp, 1] },
|
||||||
toClear
|
toClear
|
||||||
@@ -372,7 +387,12 @@ export function set(deviceData, path, timestamp, attributes, toClear) {
|
|||||||
return toClear;
|
return toClear;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function track(deviceData, path, marker, attributes) {
|
export function track(
|
||||||
|
deviceData: DeviceData,
|
||||||
|
path: ParamPath,
|
||||||
|
marker: string,
|
||||||
|
attributes?
|
||||||
|
): void {
|
||||||
path = deviceData.paths.add(path);
|
path = deviceData.paths.add(path);
|
||||||
let f = 1;
|
let f = 1;
|
||||||
|
|
||||||
@@ -388,7 +408,7 @@ export function track(deviceData, path, marker, attributes) {
|
|||||||
cur[marker] |= f;
|
cur[marker] |= f;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearTrackers(deviceData, tracker) {
|
export function clearTrackers(deviceData, tracker): 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];
|
||||||
@@ -16,18 +16,17 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
|
|
||||||
const TIMEOUT = config.get("EXT_TIMEOUT");
|
const TIMEOUT = +config.get("EXT_TIMEOUT");
|
||||||
|
|
||||||
const processes = {};
|
const processes = {};
|
||||||
const jobs = new Map();
|
const jobs = new Map();
|
||||||
|
|
||||||
function messageHandler(message) {
|
function messageHandler(message): void {
|
||||||
const func = jobs.get(message[0]);
|
const func = jobs.get(message[0]);
|
||||||
if (func) {
|
if (func) {
|
||||||
jobs.delete(message[0]);
|
jobs.delete(message[0]);
|
||||||
@@ -35,7 +34,7 @@ function messageHandler(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function run(args, callback) {
|
export function run(args, callback): boolean {
|
||||||
const scriptName = args[0];
|
const scriptName = args[0];
|
||||||
|
|
||||||
const id = crypto.randomBytes(8).toString("hex");
|
const id = crypto.randomBytes(8).toString("hex");
|
||||||
@@ -77,7 +76,7 @@ export function run(args, callback) {
|
|||||||
return processes[scriptName].send([id, args.slice(1)]);
|
return processes[scriptName].send([id, args.slice(1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function kill(process, callback) {
|
function kill(process, callback?: () => void): void {
|
||||||
if (process.signalCode !== null || process.exitCode !== null) return;
|
if (process.signalCode !== null || process.exitCode !== null) return;
|
||||||
|
|
||||||
const timeToKill = Date.now() + 5000;
|
const timeToKill = Date.now() + 5000;
|
||||||
@@ -96,7 +95,7 @@ function kill(process, callback) {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function killAll(callback) {
|
export function killAll(callback?): void {
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
for (const n in processes) {
|
for (const n in processes) {
|
||||||
++counter;
|
++counter;
|
||||||
@@ -16,14 +16,13 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import url from "url";
|
import * as url from "url";
|
||||||
import querystring from "querystring";
|
import * as querystring from "querystring";
|
||||||
import { GridFSBucket } from "mongodb";
|
import { GridFSBucket } from "mongodb";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
|
|
||||||
export function listener(request, response) {
|
export function listener(request, response): void {
|
||||||
const urlParts = url.parse(request.url, true);
|
const urlParts = url.parse(request.url, true);
|
||||||
if (request.method === "GET") {
|
if (request.method === "GET") {
|
||||||
const filename = querystring.unescape(urlParts.pathname.substring(1));
|
const filename = querystring.unescape(urlParts.pathname.substring(1));
|
||||||
@@ -46,7 +45,7 @@ export function listener(request, response) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const bucket = new GridFSBucket(db.client.db());
|
const bucket = new GridFSBucket(db.client.db());
|
||||||
const downloadStream = bucket.openDownloadStream(filename);
|
const downloadStream = bucket.openDownloadStreamByName(filename);
|
||||||
downloadStream.pipe(response);
|
downloadStream.pipe(response);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const WILDCARD_MULTIPLIER = 2;
|
const WILDCARD_MULTIPLIER = 2;
|
||||||
const UNDISCOVERED_DEPTH = 7;
|
const UNDISCOVERED_DEPTH = 7;
|
||||||
@@ -28,7 +27,7 @@ const UNDISCOVERED_DEPTH = 7;
|
|||||||
// flags is an int where its bits mark the segments in the pattern that
|
// flags is an int where its bits mark the segments in the pattern that
|
||||||
// need refreshing. Leading 0s indicate that the pattern up to that
|
// need refreshing. Leading 0s indicate that the pattern up to that
|
||||||
// point has been discovered.
|
// point has been discovered.
|
||||||
export function estimateGpnCount(gpnPatterns, depth = 0) {
|
export function estimateGpnCount(gpnPatterns, depth = 0): number {
|
||||||
const children = {};
|
const children = {};
|
||||||
const wildcardChildren = [];
|
const wildcardChildren = [];
|
||||||
let wildcardDiscovered = false;
|
let wildcardDiscovered = false;
|
||||||
@@ -16,22 +16,27 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
interface Instance {
|
||||||
|
[name: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class InstanceSet {
|
export default class InstanceSet {
|
||||||
constructor() {
|
private set: Set<Instance>;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
this.set = new Set();
|
this.set = new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
add(instance) {
|
public add(instance: Instance): void {
|
||||||
this.set.add(instance);
|
this.set.add(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(instance) {
|
public delete(instance: Instance): void {
|
||||||
this.set.delete(instance);
|
this.set.delete(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
superset(instance) {
|
public superset(instance: Instance): Instance[] {
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const inst of this.set) {
|
for (const inst of this.set) {
|
||||||
let match = true;
|
let match = true;
|
||||||
@@ -67,7 +72,7 @@ export default class InstanceSet {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
subset(instance) {
|
public subset(instance: Instance): Instance[] {
|
||||||
const res = [];
|
const res = [];
|
||||||
|
|
||||||
for (const inst of this.set) {
|
for (const inst of this.set) {
|
||||||
@@ -104,23 +109,23 @@ export default class InstanceSet {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
public [Symbol.iterator](): IterableIterator<Instance> {
|
||||||
return this.set.values();
|
return this.set.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(callback) {
|
public forEach(callback: (instance: Instance) => void): void {
|
||||||
return this.set.forEach(callback);
|
this.set.forEach(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
values() {
|
public values(): IterableIterator<Instance> {
|
||||||
return this.set.values();
|
return this.set.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
public clear(): void {
|
||||||
this.set.clear();
|
this.set.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
get size() {
|
public get size(): number {
|
||||||
return this.set.size;
|
return this.set.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,11 +16,9 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import vm from "vm";
|
|
||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
|
import * as vm from "vm";
|
||||||
|
import * as crypto from "crypto";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as cache from "./cache";
|
import * as cache from "./cache";
|
||||||
@@ -28,6 +26,7 @@ import * as query from "./query";
|
|||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import * as scheduling from "./scheduling";
|
import * as scheduling from "./scheduling";
|
||||||
import * as expression from "./common/expression";
|
import * as expression from "./common/expression";
|
||||||
|
import { Preset } from "./types";
|
||||||
|
|
||||||
const REFRESH = 3000;
|
const REFRESH = 3000;
|
||||||
const EVICT_TIMEOUT = 60000;
|
const EVICT_TIMEOUT = 60000;
|
||||||
@@ -36,7 +35,7 @@ const snapshots = new Map();
|
|||||||
let currentSnapshot = null;
|
let currentSnapshot = null;
|
||||||
let nextRefresh = 1;
|
let nextRefresh = 1;
|
||||||
|
|
||||||
function computeHash(snapshot) {
|
function computeHash(snapshot): string {
|
||||||
// MD5 hash for presets, provisions, virtual parameters for detecting changes
|
// MD5 hash for presets, provisions, virtual parameters for detecting changes
|
||||||
const h = crypto.createHash("md5");
|
const h = crypto.createHash("md5");
|
||||||
for (const p of snapshot.presets) {
|
for (const p of snapshot.presets) {
|
||||||
@@ -65,7 +64,7 @@ function computeHash(snapshot) {
|
|||||||
return h.digest("hex");
|
return h.digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenObject(src, prefix = "", dst = {}) {
|
function flattenObject(src, prefix = "", dst = {}): {} {
|
||||||
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))
|
||||||
@@ -75,7 +74,7 @@ function flattenObject(src, prefix = "", dst = {}) {
|
|||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh(callback) {
|
function refresh(callback): void {
|
||||||
if (!nextRefresh) {
|
if (!nextRefresh) {
|
||||||
return void setTimeout(() => {
|
return void setTimeout(() => {
|
||||||
refresh(callback);
|
refresh(callback);
|
||||||
@@ -126,7 +125,7 @@ function refresh(callback) {
|
|||||||
else return a.weight - b.weight;
|
else return a.weight - b.weight;
|
||||||
});
|
});
|
||||||
|
|
||||||
const presets = [];
|
const presets = [] as Preset[];
|
||||||
for (const preset of res) {
|
for (const preset of res) {
|
||||||
let schedule = null;
|
let schedule = null;
|
||||||
if (preset.schedule) {
|
if (preset.schedule) {
|
||||||
@@ -369,7 +368,16 @@ function refresh(callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentSnapshot(callback) {
|
interface GetCurrentSnapshotCallback {
|
||||||
|
(err: null, currentSnapshot: string): void;
|
||||||
|
(err: Error, currentSnapshot?: null): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentSnapshot(callback: GetCurrentSnapshotCallback);
|
||||||
|
export function getCurrentSnapshot(): Promise<string>;
|
||||||
|
export function getCurrentSnapshot(
|
||||||
|
callback?: GetCurrentSnapshotCallback
|
||||||
|
): Promise<string> | void {
|
||||||
if (Date.now() < nextRefresh) {
|
if (Date.now() < nextRefresh) {
|
||||||
if (callback) return callback(null, currentSnapshot);
|
if (callback) return callback(null, currentSnapshot);
|
||||||
return Promise.resolve(currentSnapshot);
|
return Promise.resolve(currentSnapshot);
|
||||||
@@ -389,35 +397,46 @@ export function getCurrentSnapshot(callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSnapshot(hash) {
|
export function hasSnapshot(hash): boolean {
|
||||||
return snapshots.has(hash);
|
return snapshots.has(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPresets(snapshotKey) {
|
export function getPresets(snapshotKey): 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) {
|
export function getProvisions(
|
||||||
|
snapshotKey
|
||||||
|
): { [name: string]: { md5: string; script: vm.Script } } {
|
||||||
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) {
|
export function getVirtualParameters(
|
||||||
|
snapshotKey
|
||||||
|
): { [name: string]: { md5: string; script: vm.Script } } {
|
||||||
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) {
|
export function getFiles(
|
||||||
|
snapshotKey
|
||||||
|
): { [name: string]: { length: number; md5: string; contentType: string } } {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig(snapshotKey, key, context, now) {
|
export function getConfig(
|
||||||
|
snapshotKey,
|
||||||
|
key,
|
||||||
|
context?,
|
||||||
|
now?
|
||||||
|
): string | number | boolean | null {
|
||||||
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");
|
||||||
|
|
||||||
@@ -446,21 +465,21 @@ export function getConfig(snapshotKey, key, context, now) {
|
|||||||
return expression.evaluate(snapshot.config[key], context, now || Date.now());
|
return expression.evaluate(snapshot.config[key], context, now || Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthConfig(snapshotKey) {
|
export function getAuthConfig(snapshotKey): {} {
|
||||||
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.auth;
|
return snapshot.config.auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPermissionsConfig(snapshotKey) {
|
export function getPermissionsConfig(snapshotKey): {} {
|
||||||
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.permissions;
|
return snapshot.config.permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUiConfig(snapshotKey) {
|
export function getUiConfig(snapshotKey): {} {
|
||||||
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");
|
||||||
|
|
||||||
@@ -16,10 +16,9 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import fs from "fs";
|
import * as fs from "fs";
|
||||||
import os from "os";
|
import * as os from "os";
|
||||||
|
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
|
|
||||||
@@ -28,20 +27,35 @@ const REOPEN_EVERY = 60000;
|
|||||||
const LOG_FORMAT = config.get("LOG_FORMAT");
|
const LOG_FORMAT = config.get("LOG_FORMAT");
|
||||||
const ACCESS_LOG_FORMAT = config.get("ACCESS_LOG_FORMAT") || LOG_FORMAT;
|
const ACCESS_LOG_FORMAT = config.get("ACCESS_LOG_FORMAT") || LOG_FORMAT;
|
||||||
|
|
||||||
const defaultMeta = {};
|
const defaultMeta: { [name: string]: any } = {};
|
||||||
|
|
||||||
let LOG_SYSTEMD = false;
|
let LOG_SYSTEMD = false;
|
||||||
let ACCESS_LOG_SYSTEMD = false;
|
let ACCESS_LOG_SYSTEMD = false;
|
||||||
|
|
||||||
let LOG_FILE, ACCESS_LOG_FILE;
|
let LOG_FILE, ACCESS_LOG_FILE;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-namespace */
|
||||||
|
namespace NodeJS {
|
||||||
|
export interface WritableStream {
|
||||||
|
fd?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "fs" {
|
||||||
|
interface WriteStream {
|
||||||
|
fd?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let logStream = fs.createWriteStream(null, { fd: process.stderr.fd });
|
let logStream = fs.createWriteStream(null, { fd: process.stderr.fd });
|
||||||
let logStat = fs.fstatSync(logStream.fd);
|
let logStat = fs.fstatSync(logStream.fd);
|
||||||
let accessLogStream = fs.createWriteStream(null, { fd: process.stdout.fd });
|
let accessLogStream = fs.createWriteStream(null, { fd: process.stdout.fd });
|
||||||
let accessLogStat = fs.fstatSync(accessLogStream.fd);
|
let accessLogStat = fs.fstatSync(accessLogStream.fd);
|
||||||
|
|
||||||
// Reopen if original files have been moved (e.g. logrotate)
|
// Reopen if original files have been moved (e.g. logrotate)
|
||||||
function reopen() {
|
function reopen(): void {
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
|
|
||||||
if (LOG_FILE) {
|
if (LOG_FILE) {
|
||||||
@@ -90,7 +104,7 @@ function reopen() {
|
|||||||
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init(service, version) {
|
export function init(service, version): 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}`;
|
||||||
@@ -115,9 +129,7 @@ export function init(service, version) {
|
|||||||
const JOURNAL_STREAM = process.env["JOURNAL_STREAM"];
|
const JOURNAL_STREAM = process.env["JOURNAL_STREAM"];
|
||||||
|
|
||||||
if (JOURNAL_STREAM) {
|
if (JOURNAL_STREAM) {
|
||||||
let [dev, inode] = JOURNAL_STREAM.split(":");
|
const [dev, inode] = JOURNAL_STREAM.split(":").map(parseInt);
|
||||||
dev = parseInt(dev);
|
|
||||||
inode = parseInt(inode);
|
|
||||||
|
|
||||||
LOG_SYSTEMD = logStat.dev === dev && logStat.ino === inode;
|
LOG_SYSTEMD = logStat.dev === dev && logStat.ino === inode;
|
||||||
ACCESS_LOG_SYSTEMD =
|
ACCESS_LOG_SYSTEMD =
|
||||||
@@ -129,12 +141,12 @@ export function init(service, version) {
|
|||||||
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function close() {
|
export function close(): void {
|
||||||
accessLogStream.end();
|
accessLogStream.end();
|
||||||
logStream.end();
|
logStream.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flatten(details) {
|
export function flatten(details): {} {
|
||||||
if (details.sessionContext) {
|
if (details.sessionContext) {
|
||||||
details.deviceId = details.sessionContext.deviceId;
|
details.deviceId = details.sessionContext.deviceId;
|
||||||
details.remoteAddress =
|
details.remoteAddress =
|
||||||
@@ -198,7 +210,7 @@ export function flatten(details) {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatJson(details, systemd) {
|
function formatJson(details, systemd): string {
|
||||||
if (systemd) {
|
if (systemd) {
|
||||||
let severity = "";
|
let severity = "";
|
||||||
if (details.severity === "info") severity = "<6>";
|
if (details.severity === "info") severity = "<6>";
|
||||||
@@ -211,7 +223,7 @@ function formatJson(details, systemd) {
|
|||||||
return `${JSON.stringify(flatten(details))}${os.EOL}`;
|
return `${JSON.stringify(flatten(details))}${os.EOL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSimple(details, systemd) {
|
function formatSimple(details, systemd): string {
|
||||||
flatten(details);
|
flatten(details);
|
||||||
|
|
||||||
let remote = "";
|
let remote = "";
|
||||||
@@ -255,7 +267,7 @@ function formatSimple(details, systemd) {
|
|||||||
}${meta}${os.EOL}`;
|
}${meta}${os.EOL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(details) {
|
function log(details): 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);
|
||||||
@@ -265,22 +277,22 @@ function log(details) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function info(details) {
|
export function info(details): void {
|
||||||
details.severity = "info";
|
details.severity = "info";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function warn(details) {
|
export function warn(details): void {
|
||||||
details.severity = "warn";
|
details.severity = "warn";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function error(details) {
|
export function error(details): void {
|
||||||
details.severity = "error";
|
details.severity = "error";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessLog(details) {
|
export function accessLog(details): 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);
|
||||||
@@ -290,17 +302,17 @@ export function accessLog(details) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessInfo(details) {
|
export function accessInfo(details): void {
|
||||||
details.severity = "info";
|
details.severity = "info";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessWarn(details) {
|
export function accessWarn(details): void {
|
||||||
details.severity = "warn";
|
details.severity = "warn";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessError(details) {
|
export function accessError(details): void {
|
||||||
details.severity = "error";
|
details.severity = "error";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
+31
-24
@@ -16,12 +16,11 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import url from "url";
|
import * as url from "url";
|
||||||
import mongodb from "mongodb";
|
import * as mongodb from "mongodb";
|
||||||
import querystring from "querystring";
|
import * as querystring from "querystring";
|
||||||
import vm from "vm";
|
import * as vm from "vm";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
@@ -43,7 +42,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) {
|
function throwError(err, httpResponse?): 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}`);
|
||||||
@@ -51,17 +50,20 @@ function throwError(err, httpResponse) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listener(request, response) {
|
export function listener(request, response): void {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
let bytes = 0;
|
let bytes = 0;
|
||||||
response.setHeader("GenieACS-Version", VERSION);
|
response.setHeader("GenieACS-Version", VERSION);
|
||||||
|
|
||||||
request.addListener("data", chunk => {
|
request.addListener(
|
||||||
chunks.push(chunk);
|
"data",
|
||||||
bytes += chunk.length;
|
(chunk): void => {
|
||||||
});
|
chunks.push(chunk);
|
||||||
|
bytes += chunk.length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function getBody() {
|
function getBody(): Buffer {
|
||||||
// Write all chunks into a Buffer
|
// Write all chunks into a Buffer
|
||||||
const body = Buffer.allocUnsafe(bytes);
|
const body = Buffer.allocUnsafe(bytes);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@@ -80,7 +82,7 @@ export function listener(request, response) {
|
|||||||
PRESETS_REGEX.exec(urlParts.pathname)[1]
|
PRESETS_REGEX.exec(urlParts.pathname)[1]
|
||||||
);
|
);
|
||||||
if (request.method === "PUT") {
|
if (request.method === "PUT") {
|
||||||
const preset = JSON.parse(body);
|
const preset = JSON.parse(body.toString());
|
||||||
preset._id = presetName;
|
preset._id = presetName;
|
||||||
db.presetsCollection.replaceOne(
|
db.presetsCollection.replaceOne(
|
||||||
{ _id: presetName },
|
{ _id: presetName },
|
||||||
@@ -117,7 +119,7 @@ export function listener(request, response) {
|
|||||||
OBJECTS_REGEX.exec(urlParts.pathname)[1]
|
OBJECTS_REGEX.exec(urlParts.pathname)[1]
|
||||||
);
|
);
|
||||||
if (request.method === "PUT") {
|
if (request.method === "PUT") {
|
||||||
const object = JSON.parse(body);
|
const object = JSON.parse(body.toString());
|
||||||
object._id = objectName;
|
object._id = objectName;
|
||||||
db.objectsCollection.replaceOne(
|
db.objectsCollection.replaceOne(
|
||||||
{ _id: objectName },
|
{ _id: objectName },
|
||||||
@@ -320,7 +322,7 @@ export function listener(request, response) {
|
|||||||
DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]
|
DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]
|
||||||
);
|
);
|
||||||
if (body.length) {
|
if (body.length) {
|
||||||
const task = JSON.parse(body);
|
const task = JSON.parse(body.toString());
|
||||||
task.device = deviceId;
|
task.device = deviceId;
|
||||||
apiFunctions.insertTasks(task, err => {
|
apiFunctions.insertTasks(task, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
@@ -340,7 +342,7 @@ export function listener(request, response) {
|
|||||||
|
|
||||||
const taskTimeout =
|
const taskTimeout =
|
||||||
(urlParts.query.timeout &&
|
(urlParts.query.timeout &&
|
||||||
parseInt(urlParts.query.timeout)) ||
|
parseInt(urlParts.query.timeout as string)) ||
|
||||||
config.get("DEVICE_ONLINE_THRESHOLD", deviceId);
|
config.get("DEVICE_ONLINE_THRESHOLD", deviceId);
|
||||||
|
|
||||||
apiFunctions.watchTask(
|
apiFunctions.watchTask(
|
||||||
@@ -499,15 +501,18 @@ export function listener(request, response) {
|
|||||||
const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
|
const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
|
||||||
metadata: metadata
|
metadata: metadata
|
||||||
});
|
});
|
||||||
uploadStream.end(body, err => {
|
|
||||||
if (err) return void throwError(err, response);
|
|
||||||
|
|
||||||
|
uploadStream.on("error", err => {
|
||||||
|
throwError(err, response);
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadStream.end(body, () => {
|
||||||
response.writeHead(201);
|
response.writeHead(201);
|
||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
} 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, err => {
|
bucket.delete((filename as unknown) as mongodb.ObjectId, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
@@ -577,7 +582,7 @@ export function listener(request, response) {
|
|||||||
let q = {};
|
let q = {};
|
||||||
if (urlParts.query.query) {
|
if (urlParts.query.query) {
|
||||||
try {
|
try {
|
||||||
q = JSON.parse(urlParts.query.query);
|
q = JSON.parse(urlParts.query.query as string);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
response.writeHead(400);
|
response.writeHead(400);
|
||||||
response.end(`${err.name}: ${err.message}`);
|
response.end(`${err.name}: ${err.message}`);
|
||||||
@@ -606,14 +611,14 @@ export function listener(request, response) {
|
|||||||
let projection = null;
|
let projection = null;
|
||||||
if (urlParts.query.projection) {
|
if (urlParts.query.projection) {
|
||||||
projection = {};
|
projection = {};
|
||||||
for (const p of urlParts.query.projection.split(","))
|
for (const p of (urlParts.query.projection as string).split(","))
|
||||||
projection[p.trim()] = 1;
|
projection[p.trim()] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cur = collection.find(q, { projection: projection });
|
const cur = collection.find(q, { projection: projection });
|
||||||
|
|
||||||
if (urlParts.query.sort) {
|
if (urlParts.query.sort) {
|
||||||
const s = JSON.parse(urlParts.query.sort);
|
const s = JSON.parse(urlParts.query.sort as string);
|
||||||
const sort = {};
|
const sort = {};
|
||||||
for (const [k, v] of Object.entries(s)) {
|
for (const [k, v] of Object.entries(s)) {
|
||||||
if (k[k.lastIndexOf(".") + 1] !== "_" && collectionName === "devices")
|
if (k[k.lastIndexOf(".") + 1] !== "_" && collectionName === "devices")
|
||||||
@@ -624,9 +629,11 @@ export function listener(request, response) {
|
|||||||
cur.sort(sort);
|
cur.sort(sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlParts.query.skip) cur.skip(parseInt(urlParts.query.skip));
|
if (urlParts.query.skip)
|
||||||
|
cur.skip(parseInt(urlParts.query.skip as string));
|
||||||
|
|
||||||
if (urlParts.query.limit) cur.limit(parseInt(urlParts.query.limit));
|
if (urlParts.query.limit)
|
||||||
|
cur.limit(parseInt(urlParts.query.limit as string));
|
||||||
|
|
||||||
cur.count(false, (err, total) => {
|
cur.count(false, (err, total) => {
|
||||||
if (err) return void throwError(err);
|
if (err) return void throwError(err);
|
||||||
@@ -16,19 +16,23 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
import { ParamPath } from "./types";
|
||||||
|
|
||||||
export default class PathSet {
|
export default class PathSet {
|
||||||
constructor() {
|
private lengthIndex: Set<ParamPath>[];
|
||||||
|
private fragmentIndex: Map<string, Set<ParamPath>>[];
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
this.lengthIndex = [];
|
this.lengthIndex = [];
|
||||||
this.fragmentIndex = [];
|
this.fragmentIndex = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get depth() {
|
public get depth(): number {
|
||||||
return this.lengthIndex.length;
|
return this.lengthIndex.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(path) {
|
public add(path: ParamPath): ParamPath {
|
||||||
const p = this.get(path);
|
const p = this.get(path);
|
||||||
|
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
@@ -50,7 +54,7 @@ export default class PathSet {
|
|||||||
|
|
||||||
let fragmentIndexSet = fragmentIndex.get(fragment);
|
let fragmentIndexSet = fragmentIndex.get(fragment);
|
||||||
if (!fragmentIndexSet) {
|
if (!fragmentIndexSet) {
|
||||||
fragmentIndexSet = new Set();
|
fragmentIndexSet = new Set<ParamPath>();
|
||||||
fragmentIndex.set(fragment, fragmentIndexSet);
|
fragmentIndex.set(fragment, fragmentIndexSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +69,7 @@ export default class PathSet {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(pattern) {
|
public get(pattern: ParamPath): ParamPath {
|
||||||
const lengthIndex = this.lengthIndex[pattern.length];
|
const lengthIndex = this.lengthIndex[pattern.length];
|
||||||
if (!lengthIndex || !lengthIndex.size) return null;
|
if (!lengthIndex || !lengthIndex.size) return null;
|
||||||
|
|
||||||
@@ -102,7 +106,12 @@ export default class PathSet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
find(pattern, superset = false, subset = false, depth = pattern.length) {
|
public find(
|
||||||
|
pattern: ParamPath,
|
||||||
|
superset: boolean = false,
|
||||||
|
subset: boolean = false,
|
||||||
|
depth: number = pattern.length
|
||||||
|
): ParamPath[] {
|
||||||
const res = [];
|
const res = [];
|
||||||
const groups = [[]];
|
const groups = [[]];
|
||||||
|
|
||||||
+18
-19
@@ -16,11 +16,10 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
|
|
||||||
function stringToRegexp(input, flags) {
|
function stringToRegexp(input, flags?): RegExp | false {
|
||||||
if (input.indexOf("*") === -1) return false;
|
if (input.indexOf("*") === -1) return false;
|
||||||
|
|
||||||
let output = input.replace(/[[\]\\^$.|?+()]/, "\\$&");
|
let output = input.replace(/[[\]\\^$.|?+()]/, "\\$&");
|
||||||
@@ -34,7 +33,7 @@ function stringToRegexp(input, flags) {
|
|||||||
return new RegExp(output, flags);
|
return new RegExp(output, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalize(input) {
|
function normalize(input): any {
|
||||||
if (common.typeOf(input) === common.STRING_TYPE) {
|
if (common.typeOf(input) === common.STRING_TYPE) {
|
||||||
const vals = [input];
|
const vals = [input];
|
||||||
const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
|
const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
|
||||||
@@ -53,10 +52,10 @@ function normalize(input) {
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expandValue(value) {
|
export function expandValue(value): any[] {
|
||||||
if (common.typeOf(value) === common.ARRAY_TYPE) {
|
if (common.typeOf(value) === common.ARRAY_TYPE) {
|
||||||
let a = [];
|
let a = [];
|
||||||
for (const j of value) a = a.conact(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 (common.typeOf(value) !== common.OBJECT_TYPE) {
|
||||||
const n = normalize(value);
|
const n = normalize(value);
|
||||||
@@ -89,7 +88,7 @@ export function expandValue(value) {
|
|||||||
return objs;
|
return objs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function permute(param, val) {
|
function permute(param, val): any[] {
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
const values = expandValue(val);
|
const values = expandValue(val);
|
||||||
|
|
||||||
@@ -104,12 +103,12 @@ function permute(param, val) {
|
|||||||
return conditions;
|
return conditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expand(query) {
|
export function expand(query): {} {
|
||||||
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.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) {
|
||||||
@@ -130,7 +129,7 @@ export function expand(query) {
|
|||||||
return newQuery;
|
return newQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testExpressions(params, expressions, lop) {
|
function testExpressions(params, expressions, lop): boolean {
|
||||||
for (const f of expressions) {
|
for (const f of expressions) {
|
||||||
const res = test(params, f);
|
const res = test(params, f);
|
||||||
switch (lop) {
|
switch (lop) {
|
||||||
@@ -157,7 +156,7 @@ function testExpressions(params, expressions, lop) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test(params, query) {
|
export function test(params, query): boolean {
|
||||||
let res;
|
let res;
|
||||||
for (const [k, v] of Object.entries(query)) {
|
for (const [k, v] of Object.entries(query)) {
|
||||||
if (k.charAt(0) === "$") {
|
if (k.charAt(0) === "$") {
|
||||||
@@ -170,7 +169,8 @@ export function test(params, query) {
|
|||||||
// TODO comparing array to regex, array to array, and object to object
|
// TODO comparing array to regex, array to array, and object to object
|
||||||
res = value.indexOf(v) !== -1;
|
res = value.indexOf(v) !== -1;
|
||||||
} else {
|
} else {
|
||||||
if (common.typeOf(v) === common.REGEXP_TYPE) res = v.test(value);
|
if (common.typeOf(v) === common.REGEXP_TYPE)
|
||||||
|
res = (v as RegExp).test(value);
|
||||||
else res = v === value;
|
else res = v === value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -216,7 +216,7 @@ export function test(params, query) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchType(src, dst) {
|
function matchType(src, dst): string | number | boolean {
|
||||||
const type = typeof src;
|
const type = typeof src;
|
||||||
if (type === "string") {
|
if (type === "string") {
|
||||||
return String(dst);
|
return String(dst);
|
||||||
@@ -233,7 +233,7 @@ function matchType(src, dst) {
|
|||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testFilter(obj, filter) {
|
export function testFilter(obj, filter): boolean {
|
||||||
for (const [k, v] of Object.entries(filter)) {
|
for (const [k, v] of Object.entries(filter)) {
|
||||||
const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
|
const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
|
||||||
const val = matchType(obj[param], v);
|
const val = matchType(obj[param], v);
|
||||||
@@ -264,12 +264,11 @@ export function testFilter(obj, filter) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertMongoQueryToFilters(query, filters) {
|
export function convertMongoQueryToFilters(query, filters = {}): {} {
|
||||||
filters = filters || {};
|
|
||||||
for (let [k, v] of Object.entries(query)) {
|
for (let [k, v] of Object.entries(query)) {
|
||||||
if (k[0] === "$") {
|
if (k[0] === "$") {
|
||||||
if (k === "$and")
|
if (k === "$and")
|
||||||
for (const vv of v) convertMongoQueryToFilters(vv, filters);
|
for (const vv of v as any[]) convertMongoQueryToFilters(vv, filters);
|
||||||
else throw new Error(`Operator ${k} not supported`);
|
else throw new Error(`Operator ${k} not supported`);
|
||||||
} else if (k === "_tags") {
|
} else if (k === "_tags") {
|
||||||
if (common.typeOf(v) === common.OBJECT_TYPE) {
|
if (common.typeOf(v) === common.OBJECT_TYPE) {
|
||||||
@@ -279,7 +278,7 @@ export function convertMongoQueryToFilters(query, filters) {
|
|||||||
else throw new Error(`Operator ${kk} not supported`);
|
else throw new Error(`Operator ${kk} not supported`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v = v.replace(/[^a-zA-Z0-9-]+/g, "_");
|
v = (v as string).replace(/[^a-zA-Z0-9-]+/g, "_");
|
||||||
filters[`Tags.${v}`] = true;
|
filters[`Tags.${v}`] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -346,11 +345,11 @@ export function convertMongoQueryToFilters(query, filters) {
|
|||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeQueryTypes(query, types) {
|
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) 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 (common.typeOf(v) === common.OBJECT_TYPE) {
|
||||||
for (const [kk, vv] of Object.entries(v)) {
|
for (const [kk, vv] of Object.entries(v)) {
|
||||||
@@ -16,17 +16,15 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import vm from "vm";
|
|
||||||
|
|
||||||
|
import * as vm from "vm";
|
||||||
import seedrandom from "seedrandom";
|
import seedrandom from "seedrandom";
|
||||||
|
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import * as device from "./device";
|
import * as device from "./device";
|
||||||
import * as extensions from "./extensions";
|
import * as extensions from "./extensions";
|
||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import * as scheduling from "./scheduling";
|
import * as scheduling from "./scheduling";
|
||||||
|
import { ParamPath, Fault } from "./types";
|
||||||
|
|
||||||
// Used for throwing to exit user script and commit
|
// Used for throwing to exit user script and commit
|
||||||
const COMMIT = Symbol();
|
const COMMIT = Symbol();
|
||||||
@@ -41,7 +39,7 @@ const context = vm.createContext();
|
|||||||
let state;
|
let state;
|
||||||
|
|
||||||
const runningExtensions = new WeakMap();
|
const runningExtensions = new WeakMap();
|
||||||
function runExtension(sessionContext, key, extCall, callback) {
|
function runExtension(sessionContext, key, extCall, callback): void {
|
||||||
let re = runningExtensions.get(sessionContext);
|
let re = runningExtensions.get(sessionContext);
|
||||||
if (!re) {
|
if (!re) {
|
||||||
re = {};
|
re = {};
|
||||||
@@ -62,13 +60,23 @@ function runExtension(sessionContext, key, extCall, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SandboxDate {
|
class SandboxDate {
|
||||||
constructor() {
|
public constructor(
|
||||||
if (arguments.length) return new Date(...arguments);
|
...argumentList: [
|
||||||
|
number?,
|
||||||
|
number?,
|
||||||
|
number?,
|
||||||
|
number?,
|
||||||
|
number?,
|
||||||
|
number?,
|
||||||
|
number?
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
if (argumentList.length) return new Date(...argumentList);
|
||||||
|
|
||||||
return new Date(state.sessionContext.timestamp);
|
return new Date(state.sessionContext.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static now(intervalOrCron, variance) {
|
public static now(intervalOrCron, variance): number {
|
||||||
let t = state.sessionContext.timestamp;
|
let t = state.sessionContext.timestamp;
|
||||||
|
|
||||||
if (typeof intervalOrCron === "number") {
|
if (typeof intervalOrCron === "number") {
|
||||||
@@ -93,7 +101,7 @@ class SandboxDate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function random() {
|
function random(): number {
|
||||||
if (!state.rng) state.rng = seedrandom(state.sessionContext.deviceId);
|
if (!state.rng) state.rng = seedrandom(state.sessionContext.deviceId);
|
||||||
|
|
||||||
return state.rng();
|
return state.rng();
|
||||||
@@ -104,7 +112,7 @@ random.seed = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ParameterWrapper {
|
class ParameterWrapper {
|
||||||
constructor(path, attributes, unpacked, unpackedRevision) {
|
public constructor(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() {
|
||||||
@@ -189,7 +197,11 @@ class ParameterWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function declare(path, timestamps, values) {
|
function declare(
|
||||||
|
path: ParamPath,
|
||||||
|
timestamps: { [attr: string]: number },
|
||||||
|
values: { [attr: string]: any }
|
||||||
|
): ParameterWrapper {
|
||||||
state.uncommitted = true;
|
state.uncommitted = true;
|
||||||
if (!timestamps) timestamps = {};
|
if (!timestamps) timestamps = {};
|
||||||
|
|
||||||
@@ -237,14 +249,14 @@ function declare(path, timestamps, values) {
|
|||||||
return new ParameterWrapper(parsedPath, attrs);
|
return new ParameterWrapper(parsedPath, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear(path, timestamp, attributes) {
|
function clear(path, timestamp, attributes): void {
|
||||||
state.uncommitted = true;
|
state.uncommitted = true;
|
||||||
|
|
||||||
if (state.revision === state.maxRevision)
|
if (state.revision === state.maxRevision)
|
||||||
state.clear.push([common.parsePath(path), timestamp, attributes]);
|
state.clear.push([common.parsePath(path), timestamp, attributes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function commit() {
|
function commit(): void {
|
||||||
++state.revision;
|
++state.revision;
|
||||||
state.uncommitted = false;
|
state.uncommitted = false;
|
||||||
|
|
||||||
@@ -258,7 +270,7 @@ function commit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ext() {
|
function ext(): any {
|
||||||
++state.extCounter;
|
++state.extCounter;
|
||||||
const extCall = Array.from(arguments).map(String);
|
const extCall = Array.from(arguments).map(String);
|
||||||
const key = `${state.revision}: ${JSON.stringify(extCall)}`;
|
const key = `${state.revision}: ${JSON.stringify(extCall)}`;
|
||||||
@@ -270,7 +282,7 @@ function ext() {
|
|||||||
throw EXT;
|
throw EXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(msg, meta) {
|
function log(msg, meta): 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,
|
||||||
@@ -300,12 +312,12 @@ context.random = random;
|
|||||||
vm.runInContext("Math.random = random;", context);
|
vm.runInContext("Math.random = random;", context);
|
||||||
delete context.random;
|
delete context.random;
|
||||||
|
|
||||||
function errorToFault(err) {
|
function errorToFault(err): Fault {
|
||||||
if (!err) return null;
|
if (!err) return null;
|
||||||
|
|
||||||
if (!err.name) return { code: "script", message: `${err}` };
|
if (!err.name) return { code: "script", message: `${err}` };
|
||||||
|
|
||||||
const fault = {
|
const fault: Fault = {
|
||||||
code: `script.${err.name}`,
|
code: `script.${err.name}`,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -335,7 +347,7 @@ export function run(
|
|||||||
maxRevision,
|
maxRevision,
|
||||||
callback,
|
callback,
|
||||||
extCounter = 0
|
extCounter = 0
|
||||||
) {
|
): void {
|
||||||
state = {
|
state = {
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
revision: startRevision,
|
revision: startRevision,
|
||||||
@@ -16,13 +16,11 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
import * as later from "later";
|
||||||
|
|
||||||
import later from "later";
|
function md532(str): number {
|
||||||
|
|
||||||
function md532(str) {
|
|
||||||
const digest = crypto
|
const digest = crypto
|
||||||
.createHash("md5")
|
.createHash("md5")
|
||||||
.update(str)
|
.update(str)
|
||||||
@@ -35,22 +33,22 @@ function md532(str) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function variance(deviceId, vrnc) {
|
export function variance(deviceId, vrnc): number {
|
||||||
return (md532(deviceId) >>> 0) % vrnc;
|
return (md532(deviceId) >>> 0) % vrnc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function interval(timestamp, intrvl, offset = 0) {
|
export function interval(timestamp, intrvl, offset = 0): number {
|
||||||
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
|
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCron(cronExp) {
|
export function parseCron(cronExp): 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) {
|
export function cron(timestamp, schedule, 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];
|
||||||
|
|
||||||
@@ -16,15 +16,15 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import fs from "fs";
|
import * as fs from "fs";
|
||||||
import http from "http";
|
import * as http from "http";
|
||||||
import https from "https";
|
import * as https from "https";
|
||||||
|
|
||||||
let server, listener;
|
let server: http.Server | https.Server;
|
||||||
|
let listener: (...args) => void;
|
||||||
|
|
||||||
function closeServer(timeout, callback) {
|
function closeServer(timeout, callback): void {
|
||||||
if (!server) return void callback();
|
if (!server) return void callback();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -48,13 +48,20 @@ function closeServer(timeout, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(port, networkInterface, ssl, _listener, onConnection) {
|
export function start(
|
||||||
|
port,
|
||||||
|
networkInterface,
|
||||||
|
ssl,
|
||||||
|
_listener,
|
||||||
|
onConnection?
|
||||||
|
): void {
|
||||||
listener = _listener;
|
listener = _listener;
|
||||||
|
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
const options = {
|
const options = {
|
||||||
key: fs.readFileSync(ssl.key),
|
key: fs.readFileSync(ssl.key),
|
||||||
cert: fs.readFileSync(ssl.cert)
|
cert: fs.readFileSync(ssl.cert),
|
||||||
|
ca: null
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -76,7 +83,7 @@ export function start(port, networkInterface, ssl, _listener, onConnection) {
|
|||||||
server.listen(port, networkInterface);
|
server.listen(port, networkInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stop() {
|
export function stop(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(new Error("Could not close server in a timely manner"));
|
reject(new Error("Could not close server in a timely manner"));
|
||||||
+203
-96
@@ -16,7 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
@@ -29,8 +28,20 @@ import { estimateGpnCount } from "./gpn-heuristic";
|
|||||||
import PathSet from "./path-set";
|
import PathSet from "./path-set";
|
||||||
import VersionedMap from "./versioned-map";
|
import VersionedMap from "./versioned-map";
|
||||||
import InstanceSet from "./instance-set";
|
import InstanceSet from "./instance-set";
|
||||||
|
import {
|
||||||
|
Attributes,
|
||||||
|
SessionContext,
|
||||||
|
ParamPath,
|
||||||
|
DeviceData,
|
||||||
|
VirtualParameterDeclaration,
|
||||||
|
GetAcsRequest,
|
||||||
|
SetAcsRequest,
|
||||||
|
AttributeTimestamps,
|
||||||
|
AttributeValues,
|
||||||
|
Fault
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
function initDeviceData() {
|
function initDeviceData(): DeviceData {
|
||||||
return {
|
return {
|
||||||
paths: new PathSet(),
|
paths: new PathSet(),
|
||||||
timestamps: new VersionedMap(),
|
timestamps: new VersionedMap(),
|
||||||
@@ -41,9 +52,14 @@ function initDeviceData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init(deviceId, cwmpVersion, timeout, callback) {
|
export function init(
|
||||||
|
deviceId: string,
|
||||||
|
cwmpVersion: string,
|
||||||
|
timeout: number,
|
||||||
|
callback: (err: Error, sessionContext: SessionContext) => void
|
||||||
|
): void {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const sessionContext = {
|
const sessionContext: SessionContext = {
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
deviceData: initDeviceData(),
|
deviceData: initDeviceData(),
|
||||||
@@ -63,7 +79,7 @@ export function init(deviceId, cwmpVersion, timeout, callback) {
|
|||||||
callback(null, sessionContext);
|
callback(null, sessionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadParameters(sessionContext, callback) {
|
function loadParameters(sessionContext: SessionContext, callback): void {
|
||||||
if (!sessionContext.toLoad || !sessionContext.toLoad.size)
|
if (!sessionContext.toLoad || !sessionContext.toLoad.size)
|
||||||
return void callback();
|
return void callback();
|
||||||
|
|
||||||
@@ -79,7 +95,7 @@ function loadParameters(sessionContext, callback) {
|
|||||||
// Device not available in database, mark as new
|
// Device not available in database, mark as new
|
||||||
sessionContext.new = true;
|
sessionContext.new = true;
|
||||||
loaded = [
|
loaded = [
|
||||||
[[], (1 << config.get("MAX_DEPTH", sessionContext.deviceId)) - 1]
|
[[], (1 << +config.get("MAX_DEPTH", sessionContext.deviceId)) - 1]
|
||||||
];
|
];
|
||||||
parameters = [];
|
parameters = [];
|
||||||
}
|
}
|
||||||
@@ -104,7 +120,7 @@ function loadParameters(sessionContext, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRpcId(sessionContext) {
|
function generateRpcId(sessionContext: SessionContext): string {
|
||||||
return (
|
return (
|
||||||
sessionContext.timestamp.toString(16) +
|
sessionContext.timestamp.toString(16) +
|
||||||
("0" + sessionContext.cycle.toString(16)).slice(-2) +
|
("0" + sessionContext.cycle.toString(16)).slice(-2) +
|
||||||
@@ -112,11 +128,11 @@ function generateRpcId(sessionContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inform(sessionContext, rpcReq, callback) {
|
export function inform(sessionContext: SessionContext, rpcReq, callback): void {
|
||||||
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
|
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
|
||||||
const params = [
|
const params: [ParamPath, number, Attributes][] = [
|
||||||
[
|
[
|
||||||
["DeviceID", "Manufacturer"],
|
["DeviceID", "Manufacturer"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -126,7 +142,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
["DeviceID", "OUI"],
|
["DeviceID", "OUI"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -136,7 +152,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
["DeviceID", "ProductClass"],
|
["DeviceID", "ProductClass"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -146,7 +162,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
["DeviceID", "SerialNumber"],
|
["DeviceID", "SerialNumber"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -169,7 +185,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params.push([
|
params.push([
|
||||||
["Events", "Inform"],
|
["Events", "Inform"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -180,7 +196,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
|
|
||||||
for (const e of rpcReq.event) {
|
for (const e of rpcReq.event) {
|
||||||
params.push([
|
params.push([
|
||||||
["Events", e.replace(/\s+/g, "_")],
|
["Events", e.replace(/\s+/g, "_")] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -191,7 +207,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preload DeviceID params
|
// Preload DeviceID params
|
||||||
loadPath(sessionContext, ["DeviceID", "*"]);
|
loadPath(sessionContext, ["DeviceID", "*"] as ParamPath);
|
||||||
|
|
||||||
for (const p of params) loadPath(sessionContext, p[0]);
|
for (const p of params) loadPath(sessionContext, p[0]);
|
||||||
|
|
||||||
@@ -200,7 +216,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
|
|
||||||
if (sessionContext.new) {
|
if (sessionContext.new) {
|
||||||
params.push([
|
params.push([
|
||||||
["DeviceID", "ID"],
|
["DeviceID", "ID"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -209,7 +225,7 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
params.push([
|
params.push([
|
||||||
["Events", "Registered"],
|
["Events", "Registered"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -244,7 +260,11 @@ export function inform(sessionContext, rpcReq, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transferComplete(sessionContext, rpcReq, callback) {
|
export function transferComplete(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
rpcReq,
|
||||||
|
callback
|
||||||
|
): void {
|
||||||
const revision =
|
const revision =
|
||||||
(sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1;
|
(sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1;
|
||||||
sessionContext.deviceData.timestamps.revision = revision;
|
sessionContext.deviceData.timestamps.revision = revision;
|
||||||
@@ -278,7 +298,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPath(sessionContext, ["Downloads", instance, "*"]);
|
loadPath(sessionContext, ["Downloads", instance, "*"] as ParamPath);
|
||||||
|
|
||||||
loadParameters(sessionContext, err => {
|
loadParameters(sessionContext, err => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
@@ -292,7 +312,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"LastDownload"
|
"LastDownload"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -305,7 +325,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"LastFileType"
|
"LastFileType"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -318,7 +338,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"LastFileName"
|
"LastFileName"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -331,7 +351,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"LastTargetFileName"
|
"LastTargetFileName"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -344,7 +364,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"StartTime"
|
"StartTime"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -357,7 +377,7 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"CompleteTime"
|
"CompleteTime"
|
||||||
]);
|
] as ParamPath);
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
p,
|
p,
|
||||||
@@ -372,8 +392,12 @@ export function transferComplete(sessionContext, rpcReq, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function revertDownloadParameters(sessionContext, instance, callback) {
|
function revertDownloadParameters(
|
||||||
loadPath(sessionContext, ["Downloads", instance, "*"]);
|
sessionContext: SessionContext,
|
||||||
|
instance,
|
||||||
|
callback
|
||||||
|
): void {
|
||||||
|
loadPath(sessionContext, ["Downloads", instance, "*"] as ParamPath);
|
||||||
|
|
||||||
loadParameters(sessionContext, err => {
|
loadParameters(sessionContext, err => {
|
||||||
if (err) return void callback(err);
|
if (err) return void callback(err);
|
||||||
@@ -386,7 +410,7 @@ function revertDownloadParameters(sessionContext, instance, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"LastDownload"
|
"LastDownload"
|
||||||
]);
|
] as ParamPath);
|
||||||
|
|
||||||
const lastDownload = sessionContext.deviceData.attributes.get(p);
|
const lastDownload = sessionContext.deviceData.attributes.get(p);
|
||||||
|
|
||||||
@@ -394,7 +418,7 @@ function revertDownloadParameters(sessionContext, instance, callback) {
|
|||||||
"Downloads",
|
"Downloads",
|
||||||
instance,
|
instance,
|
||||||
"Download"
|
"Download"
|
||||||
]);
|
] as ParamPath);
|
||||||
|
|
||||||
const toClear = device.set(sessionContext.deviceData, p, timestamp, {
|
const toClear = device.set(sessionContext.deviceData, p, timestamp, {
|
||||||
value: [
|
value: [
|
||||||
@@ -410,7 +434,10 @@ function revertDownloadParameters(sessionContext, instance, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function timeoutOperations(sessionContext, callback) {
|
export function timeoutOperations(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
callback
|
||||||
|
): void {
|
||||||
const revision =
|
const revision =
|
||||||
(sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1;
|
(sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1;
|
||||||
sessionContext.deviceData.timestamps.revision = revision;
|
sessionContext.deviceData.timestamps.revision = revision;
|
||||||
@@ -427,7 +454,7 @@ export function timeoutOperations(sessionContext, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DOWNLOAD_TIMEOUT =
|
const DOWNLOAD_TIMEOUT =
|
||||||
localCache.getConfig(
|
+localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.downloadTimeout",
|
"cwmp.downloadTimeout",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
@@ -460,11 +487,15 @@ export function timeoutOperations(sessionContext, callback) {
|
|||||||
if ((counter -= 2) === 1) callback(null, faults, operations);
|
if ((counter -= 2) === 1) callback(null, faults, operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addProvisions(sessionContext, channel, provisions) {
|
export function addProvisions(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
channel,
|
||||||
|
provisions
|
||||||
|
): void {
|
||||||
// Multiply by two because every iteration is two
|
// Multiply by two because every iteration is two
|
||||||
// phases: read and update
|
// phases: read and update
|
||||||
const MAX_ITERATIONS =
|
const MAX_ITERATIONS =
|
||||||
localCache.getConfig(
|
+localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.maxCommitIterations",
|
"cwmp.maxCommitIterations",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
@@ -513,11 +544,11 @@ export function addProvisions(sessionContext, channel, provisions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearProvisions(sessionContext) {
|
export function clearProvisions(sessionContext: SessionContext): void {
|
||||||
// Multiply by two because every iteration is two
|
// Multiply by two because every iteration is two
|
||||||
// phases: read and update
|
// phases: read and update
|
||||||
const MAX_ITERATIONS =
|
const MAX_ITERATIONS =
|
||||||
localCache.getConfig(
|
+localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.maxCommitIterations",
|
"cwmp.maxCommitIterations",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
@@ -551,7 +582,7 @@ function runProvisions(
|
|||||||
startRevision,
|
startRevision,
|
||||||
endRevision,
|
endRevision,
|
||||||
callback
|
callback
|
||||||
) {
|
): void {
|
||||||
let done = true;
|
let done = true;
|
||||||
let allDeclarations = [];
|
let allDeclarations = [];
|
||||||
let allClear = [];
|
let allClear = [];
|
||||||
@@ -616,7 +647,7 @@ function runVirtualParameters(
|
|||||||
startRevision,
|
startRevision,
|
||||||
endRevision,
|
endRevision,
|
||||||
callback
|
callback
|
||||||
) {
|
): void {
|
||||||
let done = true;
|
let done = true;
|
||||||
const virtualParameterUpdates = [];
|
const virtualParameterUpdates = [];
|
||||||
let allDeclarations = [];
|
let allDeclarations = [];
|
||||||
@@ -660,7 +691,11 @@ function runVirtualParameters(
|
|||||||
return void (counter = 0);
|
return void (counter = 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = {};
|
const ret: {
|
||||||
|
writable?: number;
|
||||||
|
value?: [string | number | boolean, string?];
|
||||||
|
} = {};
|
||||||
|
|
||||||
if (_returnValue.writable != null) {
|
if (_returnValue.writable != null) {
|
||||||
ret.writable = +!!_returnValue.writable;
|
ret.writable = +!!_returnValue.writable;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -758,7 +793,10 @@ function runVirtualParameters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runDeclarations(sessionContext, declarations) {
|
function runDeclarations(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
declarations
|
||||||
|
): VirtualParameterDeclaration[] {
|
||||||
if (!sessionContext.syncState) {
|
if (!sessionContext.syncState) {
|
||||||
sessionContext.syncState = {
|
sessionContext.syncState = {
|
||||||
refreshAttributes: {
|
refreshAttributes: {
|
||||||
@@ -768,7 +806,7 @@ function runDeclarations(sessionContext, declarations) {
|
|||||||
value: new Set()
|
value: new Set()
|
||||||
},
|
},
|
||||||
spv: new Map(),
|
spv: new Map(),
|
||||||
gpn: new Set(),
|
gpn: new Set<ParamPath>(),
|
||||||
gpnPatterns: new Map(),
|
gpnPatterns: new Map(),
|
||||||
tags: new Map(),
|
tags: new Map(),
|
||||||
virtualParameterDeclarations: [],
|
virtualParameterDeclarations: [],
|
||||||
@@ -782,14 +820,20 @@ function runDeclarations(sessionContext, declarations) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const allDeclareTimestamps = new Map();
|
const allDeclareTimestamps = new Map();
|
||||||
const allDeclareAttributeTimestamps = new Map();
|
const allDeclareAttributeTimestamps = new Map<
|
||||||
const allDeclareAttributeValues = new Map();
|
ParamPath,
|
||||||
|
AttributeTimestamps
|
||||||
|
>();
|
||||||
|
const allDeclareAttributeValues = new Map<ParamPath, AttributeValues>();
|
||||||
|
|
||||||
const allVirtualParameters = localCache.getVirtualParameters(
|
const allVirtualParameters = localCache.getVirtualParameters(
|
||||||
sessionContext.cacheSnapshot
|
sessionContext.cacheSnapshot
|
||||||
);
|
);
|
||||||
|
|
||||||
function mergeAttributeTimestamps(p, attrs) {
|
function mergeAttributeTimestamps(
|
||||||
|
p: ParamPath,
|
||||||
|
attrs: AttributeTimestamps
|
||||||
|
): void {
|
||||||
let cur = allDeclareAttributeTimestamps.get(p);
|
let cur = allDeclareAttributeTimestamps.get(p);
|
||||||
if (!cur) {
|
if (!cur) {
|
||||||
allDeclareAttributeTimestamps.set(p, attrs);
|
allDeclareAttributeTimestamps.set(p, attrs);
|
||||||
@@ -801,7 +845,11 @@ function runDeclarations(sessionContext, declarations) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeAttributeValues(p, attrs, defer) {
|
function mergeAttributeValues(
|
||||||
|
p: ParamPath,
|
||||||
|
attrs: AttributeValues,
|
||||||
|
defer: boolean
|
||||||
|
): void {
|
||||||
let cur = allDeclareAttributeValues.get(p);
|
let cur = allDeclareAttributeValues.get(p);
|
||||||
if (!cur) {
|
if (!cur) {
|
||||||
if (!defer) allDeclareAttributeValues.set(p, attrs);
|
if (!defer) allDeclareAttributeValues.set(p, attrs);
|
||||||
@@ -816,19 +864,26 @@ function runDeclarations(sessionContext, declarations) {
|
|||||||
let unpacked;
|
let unpacked;
|
||||||
|
|
||||||
if ((path.alias | path.wildcard) & 1 || path[0] === "VirtualParameters") {
|
if ((path.alias | path.wildcard) & 1 || path[0] === "VirtualParameters") {
|
||||||
sessionContext.deviceData.paths.add(["VirtualParameters"]);
|
sessionContext.deviceData.paths.add(["VirtualParameters"] as ParamPath);
|
||||||
if ((path.alias | path.wildcard) & 2) {
|
if ((path.alias | path.wildcard) & 2) {
|
||||||
sessionContext.deviceData.paths.add(["VirtualParameters", "*"]);
|
sessionContext.deviceData.paths.add([
|
||||||
for (const k of Object.keys(allVirtualParameters))
|
"VirtualParameters",
|
||||||
sessionContext.deviceData.paths.add(["VirtualParameters", k]);
|
"*"
|
||||||
|
] as ParamPath);
|
||||||
|
for (const k of Object.keys(allVirtualParameters)) {
|
||||||
|
sessionContext.deviceData.paths.add([
|
||||||
|
"VirtualParameters",
|
||||||
|
k
|
||||||
|
] as ParamPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((path.alias | path.wildcard) & 1 || path[0] === "Reboot")
|
if ((path.alias | path.wildcard) & 1 || path[0] === "Reboot")
|
||||||
sessionContext.deviceData.paths.add(["Reboot"]);
|
sessionContext.deviceData.paths.add(["Reboot"] as ParamPath);
|
||||||
|
|
||||||
if ((path.alias | path.wildcard) & 1 || path[0] === "FactoryReset")
|
if ((path.alias | path.wildcard) & 1 || path[0] === "FactoryReset")
|
||||||
sessionContext.deviceData.paths.add(["FactoryReset"]);
|
sessionContext.deviceData.paths.add(["FactoryReset"] as ParamPath);
|
||||||
|
|
||||||
if (path.alias) {
|
if (path.alias) {
|
||||||
const aliasDecs = device.getAliasDeclarations(
|
const aliasDecs = device.getAliasDeclarations(
|
||||||
@@ -958,7 +1013,11 @@ function runDeclarations(sessionContext, declarations) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rpcRequest(sessionContext, _declarations, callback) {
|
export function rpcRequest(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
_declarations,
|
||||||
|
callback
|
||||||
|
): void {
|
||||||
if (sessionContext.rpcRequest != null) {
|
if (sessionContext.rpcRequest != null) {
|
||||||
return void callback(
|
return void callback(
|
||||||
null,
|
null,
|
||||||
@@ -1097,7 +1156,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
// Multiply by two because every iteration is two
|
// Multiply by two because every iteration is two
|
||||||
// phases: read and update
|
// phases: read and update
|
||||||
const MAX_ITERATIONS =
|
const MAX_ITERATIONS =
|
||||||
localCache.getConfig(
|
+localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.maxCommitIterations",
|
"cwmp.maxCommitIterations",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
@@ -1162,7 +1221,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
for (const k of Object.keys(allVirtualParameters)) {
|
for (const k of Object.keys(allVirtualParameters)) {
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["VirtualParameters", k],
|
["VirtualParameters", k] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0]
|
object: [timestamp, 0]
|
||||||
@@ -1272,7 +1331,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
if (index == null) {
|
if (index == null) {
|
||||||
index = 0;
|
index = 0;
|
||||||
for (const p of sessionContext.deviceData.paths.find(
|
for (const p of sessionContext.deviceData.paths.find(
|
||||||
["Downloads", "*"],
|
["Downloads", "*"] as ParamPath,
|
||||||
false,
|
false,
|
||||||
true
|
true
|
||||||
)) {
|
)) {
|
||||||
@@ -1285,7 +1344,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads"],
|
["Downloads"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1], writable: [timestamp, 1] },
|
{ object: [timestamp, 1], writable: [timestamp, 1] },
|
||||||
toClear
|
toClear
|
||||||
@@ -1293,7 +1352,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", "" + index],
|
["Downloads", "" + index] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1], writable: [timestamp, 1] },
|
{ object: [timestamp, 1], writable: [timestamp, 1] },
|
||||||
toClear
|
toClear
|
||||||
@@ -1327,7 +1386,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
for (const [k, v] of Object.entries(params)) {
|
for (const [k, v] of Object.entries(params)) {
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", `${index}`, k],
|
["Downloads", `${index}`, k] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
@@ -1340,7 +1399,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", `${index}`, "*"],
|
["Downloads", `${index}`, "*"] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
null,
|
null,
|
||||||
toClear
|
toClear
|
||||||
@@ -1445,7 +1504,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["VirtualParameters", vparams[i][0]],
|
["VirtualParameters", vparams[i][0]] as ParamPath,
|
||||||
timestamp,
|
timestamp,
|
||||||
vpu,
|
vpu,
|
||||||
toClear
|
toClear
|
||||||
@@ -1458,7 +1517,7 @@ export function rpcRequest(sessionContext, _declarations, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGetRpcRequest(sessionContext) {
|
function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
|
||||||
const syncState = sessionContext.syncState;
|
const syncState = sessionContext.syncState;
|
||||||
if (!syncState) return null;
|
if (!syncState) return null;
|
||||||
|
|
||||||
@@ -1482,7 +1541,10 @@ function generateGetRpcRequest(sessionContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
|
const p = sessionContext.deviceData.paths.add(path.slice(
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
) as ParamPath);
|
||||||
syncState.gpn.add(p);
|
syncState.gpn.add(p);
|
||||||
const f = 1 << p.length;
|
const f = 1 << p.length;
|
||||||
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
||||||
@@ -1510,7 +1572,10 @@ function generateGetRpcRequest(sessionContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
|
const p = sessionContext.deviceData.paths.add(path.slice(
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
) as ParamPath);
|
||||||
syncState.gpn.add(p);
|
syncState.gpn.add(p);
|
||||||
const f = 1 << p.length;
|
const f = 1 << p.length;
|
||||||
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
||||||
@@ -1519,7 +1584,10 @@ function generateGetRpcRequest(sessionContext) {
|
|||||||
syncState.refreshAttributes.object.clear();
|
syncState.refreshAttributes.object.clear();
|
||||||
|
|
||||||
for (const path of syncState.refreshAttributes.writable) {
|
for (const path of syncState.refreshAttributes.writable) {
|
||||||
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
|
const p = sessionContext.deviceData.paths.add(path.slice(
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
) as ParamPath);
|
||||||
syncState.gpn.add(p);
|
syncState.gpn.add(p);
|
||||||
const f = 1 << p.length;
|
const f = 1 << p.length;
|
||||||
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
|
||||||
@@ -1613,7 +1681,7 @@ function generateGetRpcRequest(sessionContext) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSetRpcRequest(sessionContext) {
|
function generateSetRpcRequest(sessionContext): SetAcsRequest {
|
||||||
const syncState = sessionContext.syncState;
|
const syncState = sessionContext.syncState;
|
||||||
if (!syncState) return null;
|
if (!syncState) return null;
|
||||||
|
|
||||||
@@ -1653,19 +1721,23 @@ function generateSetRpcRequest(sessionContext) {
|
|||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
);
|
);
|
||||||
|
|
||||||
const DATETIME_MILLISECONDS = localCache.getConfig(
|
const DATETIME_MILLISECONDS = !!localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.datetimeMilliseconds",
|
"cwmp.datetimeMilliseconds",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
);
|
);
|
||||||
|
|
||||||
const BOOLEAN_LITERAL = localCache.getConfig(
|
const BOOLEAN_LITERAL = !!localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.booleanLiteral",
|
"cwmp.booleanLiteral",
|
||||||
sessionContext.configContext
|
sessionContext.configContext
|
||||||
);
|
);
|
||||||
|
|
||||||
const parameterValues = [];
|
const parameterValues = [] as [
|
||||||
|
ParamPath,
|
||||||
|
string | number | boolean,
|
||||||
|
string
|
||||||
|
][];
|
||||||
for (const [k, v] of syncState.spv) {
|
for (const [k, v] of syncState.spv) {
|
||||||
syncState.spv.delete(k);
|
syncState.spv.delete(k);
|
||||||
const attrs = sessionContext.deviceData.attributes.get(k);
|
const attrs = sessionContext.deviceData.attributes.get(k);
|
||||||
@@ -1693,7 +1765,14 @@ function generateSetRpcRequest(sessionContext) {
|
|||||||
if (parameterValues.length) {
|
if (parameterValues.length) {
|
||||||
return {
|
return {
|
||||||
name: "SetParameterValues",
|
name: "SetParameterValues",
|
||||||
parameterList: parameterValues.map(p => [p[0].join("."), p[1], p[2]]),
|
parameterList: parameterValues.map(
|
||||||
|
p =>
|
||||||
|
[p[0].join("."), p[1], p[2]] as [
|
||||||
|
string,
|
||||||
|
string | number | boolean,
|
||||||
|
string
|
||||||
|
]
|
||||||
|
),
|
||||||
DATETIME_MILLISECONDS: DATETIME_MILLISECONDS,
|
DATETIME_MILLISECONDS: DATETIME_MILLISECONDS,
|
||||||
BOOLEAN_LITERAL: BOOLEAN_LITERAL
|
BOOLEAN_LITERAL: BOOLEAN_LITERAL
|
||||||
};
|
};
|
||||||
@@ -1764,7 +1843,13 @@ function generateSetRpcRequest(sessionContext) {
|
|||||||
function generateGetVirtualParameterProvisions(
|
function generateGetVirtualParameterProvisions(
|
||||||
sessionContext,
|
sessionContext,
|
||||||
virtualParameterDeclarations
|
virtualParameterDeclarations
|
||||||
) {
|
): [
|
||||||
|
ParamPath,
|
||||||
|
AttributeTimestamps,
|
||||||
|
AttributeValues,
|
||||||
|
AttributeTimestamps,
|
||||||
|
AttributeValues
|
||||||
|
][] {
|
||||||
let provisions;
|
let provisions;
|
||||||
if (virtualParameterDeclarations) {
|
if (virtualParameterDeclarations) {
|
||||||
for (const declaration of virtualParameterDeclarations) {
|
for (const declaration of virtualParameterDeclarations) {
|
||||||
@@ -1804,7 +1889,13 @@ function generateGetVirtualParameterProvisions(
|
|||||||
function generateSetVirtualParameterProvisions(
|
function generateSetVirtualParameterProvisions(
|
||||||
sessionContext,
|
sessionContext,
|
||||||
virtualParameterDeclarations
|
virtualParameterDeclarations
|
||||||
) {
|
): [
|
||||||
|
ParamPath,
|
||||||
|
AttributeTimestamps,
|
||||||
|
AttributeValues,
|
||||||
|
AttributeTimestamps,
|
||||||
|
AttributeValues
|
||||||
|
][] {
|
||||||
let provisions;
|
let provisions;
|
||||||
if (virtualParameterDeclarations) {
|
if (virtualParameterDeclarations) {
|
||||||
for (const declaration of virtualParameterDeclarations) {
|
for (const declaration of virtualParameterDeclarations) {
|
||||||
@@ -1850,21 +1941,27 @@ function generateSetVirtualParameterProvisions(
|
|||||||
function processDeclarations(
|
function processDeclarations(
|
||||||
sessionContext,
|
sessionContext,
|
||||||
allDeclareTimestamps,
|
allDeclareTimestamps,
|
||||||
allDeclareAttributeTimestamps,
|
allDeclareAttributeTimestamps: Map<ParamPath, AttributeTimestamps>,
|
||||||
allDeclareAttributeValues
|
allDeclareAttributeValues: Map<ParamPath, AttributeValues>
|
||||||
) {
|
): VirtualParameterDeclaration[] {
|
||||||
const deviceData = sessionContext.deviceData;
|
const deviceData = sessionContext.deviceData;
|
||||||
const syncState = sessionContext.syncState;
|
const syncState = sessionContext.syncState;
|
||||||
|
|
||||||
const root = sessionContext.deviceData.paths.add([]);
|
const root = sessionContext.deviceData.paths.add([]);
|
||||||
const paths = deviceData.paths.find([], false, true, 99);
|
const paths = deviceData.paths.find([], false, true, 99);
|
||||||
paths.sort((a, b) =>
|
paths.sort(
|
||||||
a.wildcard === b.wildcard ? a.length - b.length : a.wildcard - b.wildcard
|
(a, b): number =>
|
||||||
|
a.wildcard === b.wildcard ? a.length - b.length : a.wildcard - b.wildcard
|
||||||
);
|
);
|
||||||
|
|
||||||
const virtualParameterDeclarations = [];
|
const virtualParameterDeclarations = [] as VirtualParameterDeclaration[];
|
||||||
|
|
||||||
function func(leafParam, leafIsObject, leafTimestamp, _paths) {
|
function func(
|
||||||
|
leafParam: ParamPath,
|
||||||
|
leafIsObject: number,
|
||||||
|
leafTimestamp: number,
|
||||||
|
_paths: ParamPath[]
|
||||||
|
): void {
|
||||||
const currentPath = _paths[0];
|
const currentPath = _paths[0];
|
||||||
const children = new Map();
|
const children = new Map();
|
||||||
let declareTimestamp = 0;
|
let declareTimestamp = 0;
|
||||||
@@ -2122,7 +2219,11 @@ function processDeclarations(
|
|||||||
return virtualParameterDeclarations;
|
return virtualParameterDeclarations;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPath(sessionContext, path, depth) {
|
function loadPath(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
path: ParamPath,
|
||||||
|
depth?: number
|
||||||
|
): boolean {
|
||||||
depth = depth || (1 << path.length) - 1;
|
depth = depth || (1 << path.length) - 1;
|
||||||
if (sessionContext.new || !depth) return true;
|
if (sessionContext.new || !depth) return true;
|
||||||
|
|
||||||
@@ -2132,12 +2233,13 @@ function loadPath(sessionContext, path, depth) {
|
|||||||
let trimWildcard = path.length;
|
let trimWildcard = path.length;
|
||||||
while (trimWildcard && path[trimWildcard - 1] === "*") --trimWildcard;
|
while (trimWildcard && path[trimWildcard - 1] === "*") --trimWildcard;
|
||||||
|
|
||||||
if (trimWildcard < path.length) path = path.slice(0, trimWildcard);
|
if (trimWildcard < path.length)
|
||||||
|
path = path.slice(0, trimWildcard) as ParamPath;
|
||||||
|
|
||||||
for (let i = 0; i <= path.length; ++i) {
|
for (let i = 0; i <= path.length; ++i) {
|
||||||
const d = i === path.length ? 99 : i;
|
const d = i === path.length ? 99 : i;
|
||||||
for (const sup of sessionContext.deviceData.paths.find(
|
for (const sup of sessionContext.deviceData.paths.find(
|
||||||
path.slice(0, i),
|
path.slice(0, i) as ParamPath,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
d
|
d
|
||||||
@@ -2165,7 +2267,7 @@ function processInstances(
|
|||||||
minInstances,
|
minInstances,
|
||||||
maxInstances,
|
maxInstances,
|
||||||
defer
|
defer
|
||||||
) {
|
): void {
|
||||||
let instancesToCreate, instancesToDelete;
|
let instancesToCreate, instancesToDelete;
|
||||||
if (parent[0] === "Downloads") {
|
if (parent[0] === "Downloads") {
|
||||||
if (parent.length !== 1) return;
|
if (parent.length !== 1) return;
|
||||||
@@ -2217,10 +2319,10 @@ function processInstances(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear(sessionContext, toClear, callback) {
|
function clear(sessionContext, toClear, callback): void {
|
||||||
if (!toClear || !toClear.length) return void callback();
|
if (!toClear || !toClear.length) return void callback();
|
||||||
|
|
||||||
const MAX_DEPTH = config.get("MAX_DEPTH", sessionContext.deviceId);
|
const MAX_DEPTH = +config.get("MAX_DEPTH", sessionContext.deviceId);
|
||||||
|
|
||||||
for (const c of toClear) {
|
for (const c of toClear) {
|
||||||
if (c[1]) {
|
if (c[1]) {
|
||||||
@@ -2250,7 +2352,7 @@ function clear(sessionContext, toClear, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
export function rpcResponse(sessionContext, id, rpcRes, callback): void {
|
||||||
if (id !== generateRpcId(sessionContext))
|
if (id !== generateRpcId(sessionContext))
|
||||||
return void callback(new Error("Request ID not recognized"));
|
return void callback(new Error("Request ID not recognized"));
|
||||||
|
|
||||||
@@ -2398,7 +2500,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
if (rpcReq.nextLevel) {
|
if (rpcReq.nextLevel) {
|
||||||
loadPath(sessionContext, root, (1 << (root.length + 1)) - 1);
|
loadPath(sessionContext, root, (1 << (root.length + 1)) - 1);
|
||||||
} else {
|
} else {
|
||||||
const MAX_DEPTH = config.get("MAX_DEPTH", sessionContext.deviceId);
|
const MAX_DEPTH = +config.get("MAX_DEPTH", sessionContext.deviceId);
|
||||||
loadPath(sessionContext, root, (1 << MAX_DEPTH) - 1);
|
loadPath(sessionContext, root, (1 << MAX_DEPTH) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2495,7 +2597,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
case "DownloadResponse":
|
case "DownloadResponse":
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "Download"],
|
["Downloads", rpcReq.instance, "Download"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
|
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2504,7 +2606,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
if (rpcRes.status === 0) {
|
if (rpcRes.status === 0) {
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "LastDownload"],
|
["Downloads", rpcReq.instance, "LastDownload"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{
|
{
|
||||||
value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]]
|
value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]]
|
||||||
@@ -2514,7 +2616,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "LastFileType"],
|
["Downloads", rpcReq.instance, "LastFileType"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2522,7 +2624,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "LastFileName"],
|
["Downloads", rpcReq.instance, "LastFileName"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2530,7 +2632,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "LastTargetFileName"],
|
["Downloads", rpcReq.instance, "LastTargetFileName"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2538,7 +2640,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "StartTime"],
|
["Downloads", rpcReq.instance, "StartTime"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [+rpcRes.startTime, "xsd:dateTime"]] },
|
{ value: [timestamp + 1, [+rpcRes.startTime, "xsd:dateTime"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2546,7 +2648,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
|
|
||||||
toClear = device.set(
|
toClear = device.set(
|
||||||
sessionContext.deviceData,
|
sessionContext.deviceData,
|
||||||
["Downloads", rpcReq.instance, "CompleteTime"],
|
["Downloads", rpcReq.instance, "CompleteTime"] as ParamPath,
|
||||||
timestamp + 1,
|
timestamp + 1,
|
||||||
{ value: [timestamp + 1, [+rpcRes.completeTime, "xsd:dateTime"]] },
|
{ value: [timestamp + 1, [+rpcRes.completeTime, "xsd:dateTime"]] },
|
||||||
toClear
|
toClear
|
||||||
@@ -2583,7 +2685,12 @@ export function rpcResponse(sessionContext, id, rpcRes, callback) {
|
|||||||
clear(sessionContext, toClear, callback);
|
clear(sessionContext, toClear, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rpcFault(sessionContext, id, faultResponse, callback) {
|
export function rpcFault(
|
||||||
|
sessionContext,
|
||||||
|
id,
|
||||||
|
faultResponse,
|
||||||
|
callback: (err: Error, fault?: Fault) => void
|
||||||
|
): void {
|
||||||
const rpcReq = sessionContext.rpcRequest;
|
const rpcReq = sessionContext.rpcRequest;
|
||||||
delete sessionContext.syncState;
|
delete sessionContext.syncState;
|
||||||
delete sessionContext.rpcRequest;
|
delete sessionContext.rpcRequest;
|
||||||
@@ -2627,7 +2734,7 @@ export function rpcFault(sessionContext, id, faultResponse, callback) {
|
|||||||
callback(null, fault);
|
callback(null, fault);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deserialize(sessionContextString, callback) {
|
export function deserialize(sessionContextString, callback): void {
|
||||||
const sessionContext = JSON.parse(sessionContextString);
|
const sessionContext = JSON.parse(sessionContextString);
|
||||||
|
|
||||||
for (const decs of sessionContext.declarations)
|
for (const decs of sessionContext.declarations)
|
||||||
@@ -2654,7 +2761,7 @@ export function deserialize(sessionContextString, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serialize(sessionContext, callback) {
|
export function serialize(sessionContext, callback): void {
|
||||||
const deviceData = [];
|
const deviceData = [];
|
||||||
|
|
||||||
for (const path of sessionContext.deviceData.paths.find(
|
for (const path of sessionContext.deviceData.paths.find(
|
||||||
+93
-54
@@ -16,11 +16,21 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import libxmljs from "libxmljs";
|
import { parseXml, Document, Element, ParserOptions } from "libxmljs";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import { version as VERSION } from "../package.json";
|
import { version as VERSION } from "../package.json";
|
||||||
|
import {
|
||||||
|
CpeResponse,
|
||||||
|
CpeGetResponse,
|
||||||
|
CpeSetResponse,
|
||||||
|
InformRequest,
|
||||||
|
AcsResponse,
|
||||||
|
CpeRequest,
|
||||||
|
FaultStruct,
|
||||||
|
SpvFault,
|
||||||
|
CpeFault
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
const SERVER_NAME = `GenieACS/${VERSION}`;
|
const SERVER_NAME = `GenieACS/${VERSION}`;
|
||||||
|
|
||||||
@@ -62,21 +72,36 @@ const NAMESPACES = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIBXMLJS_PARSE_OPTIONS = {
|
const LIBXMLJS_PARSE_OPTIONS: ParserOptions = {
|
||||||
nocdata: true,
|
nocdata: true,
|
||||||
recover: config.get("XML_RECOVER"),
|
recover: !!config.get("XML_RECOVER"),
|
||||||
ignore_enc: config.get("XML_IGNORE_ENC")
|
/* eslint-disable-next-line @typescript-eslint/camelcase */
|
||||||
|
ignore_enc: !!config.get("XML_IGNORE_ENC")
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIBXMLJS_SAVE_OPTIONS = {
|
interface WriterOptions {
|
||||||
format: config.get("XML_FORMAT"),
|
declaration?: boolean;
|
||||||
|
format?: boolean;
|
||||||
|
selfCloseEmpty?: boolean;
|
||||||
|
whitespace?: boolean;
|
||||||
|
type?: "XML" | "HTML" | "XHTML";
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "libxmljs" {
|
||||||
|
interface Document {
|
||||||
|
toString(formatting: boolean | WriterOptions): string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIBXMLJS_SAVE_OPTIONS: WriterOptions = {
|
||||||
|
format: !!config.get("XML_FORMAT"),
|
||||||
declaration: !config.get("XML_NO_DECL"),
|
declaration: !config.get("XML_NO_DECL"),
|
||||||
selfCloseEmpty: !config.get("XML_NO_EMPTY")
|
selfCloseEmpty: !config.get("XML_NO_EMPTY")
|
||||||
};
|
};
|
||||||
|
|
||||||
let warnings;
|
let warnings;
|
||||||
|
|
||||||
function parseBool(v) {
|
function parseBool(v): boolean {
|
||||||
v = "" + v;
|
v = "" + v;
|
||||||
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
||||||
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
||||||
@@ -84,7 +109,7 @@ function parseBool(v) {
|
|||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function event(xml) {
|
function event(xml): string[] {
|
||||||
return xml
|
return xml
|
||||||
.childNodes()
|
.childNodes()
|
||||||
.filter(n => n.name() === "EventStruct")
|
.filter(n => n.name() === "EventStruct")
|
||||||
@@ -97,7 +122,7 @@ function event(xml) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parameterInfoList(xml) {
|
function parameterInfoList(xml): [string, boolean][] {
|
||||||
return xml
|
return xml
|
||||||
.childNodes()
|
.childNodes()
|
||||||
.filter(e => e.name() === "ParameterInfoStruct")
|
.filter(e => e.name() === "ParameterInfoStruct")
|
||||||
@@ -128,7 +153,9 @@ function parameterInfoList(xml) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parameterValueList(xml) {
|
function parameterValueList(
|
||||||
|
xml
|
||||||
|
): [string, string | number | boolean, string][] {
|
||||||
return xml
|
return xml
|
||||||
.childNodes()
|
.childNodes()
|
||||||
.filter(e => e.name() === "ParameterValueStruct")
|
.filter(e => e.name() === "ParameterValueStruct")
|
||||||
@@ -181,13 +208,13 @@ function parameterValueList(xml) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterNames(xml, methodRequest) {
|
function GetParameterNames(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:GetParameterNames");
|
const el = xml.node("cwmp:GetParameterNames");
|
||||||
el.node("ParameterPath").text(methodRequest.parameterPath);
|
el.node("ParameterPath").text(methodRequest.parameterPath);
|
||||||
el.node("NextLevel").text(+methodRequest.nextLevel);
|
el.node("NextLevel").text(+methodRequest.nextLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterNamesResponse(xml) {
|
function GetParameterNamesResponse(xml): CpeGetResponse {
|
||||||
return {
|
return {
|
||||||
name: "GetParameterNamesResponse",
|
name: "GetParameterNamesResponse",
|
||||||
parameterList: parameterInfoList(
|
parameterList: parameterInfoList(
|
||||||
@@ -196,7 +223,7 @@ function GetParameterNamesResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterValues(xml, methodRequest) {
|
function GetParameterValues(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:GetParameterValues").node("ParameterNames");
|
const el = xml.node("cwmp:GetParameterValues").node("ParameterNames");
|
||||||
el.attr({
|
el.attr({
|
||||||
"soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]`
|
"soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]`
|
||||||
@@ -205,7 +232,7 @@ function GetParameterValues(xml, methodRequest) {
|
|||||||
for (const p of methodRequest.parameterNames) el.node("string").text(p);
|
for (const p of methodRequest.parameterNames) el.node("string").text(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterValuesResponse(xml) {
|
function GetParameterValuesResponse(xml): CpeGetResponse {
|
||||||
return {
|
return {
|
||||||
name: "GetParameterValuesResponse",
|
name: "GetParameterValuesResponse",
|
||||||
parameterList: parameterValueList(
|
parameterList: parameterValueList(
|
||||||
@@ -214,7 +241,7 @@ function GetParameterValuesResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterValues(xml, methodRequest) {
|
function SetParameterValues(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:SetParameterValues");
|
const el = xml.node("cwmp:SetParameterValues");
|
||||||
const paramList = el.node("ParameterList");
|
const paramList = el.node("ParameterList");
|
||||||
paramList.attr({
|
paramList.attr({
|
||||||
@@ -244,7 +271,7 @@ function SetParameterValues(xml, methodRequest) {
|
|||||||
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterValuesResponse(xml) {
|
function SetParameterValuesResponse(xml): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "SetParameterValuesResponse",
|
name: "SetParameterValuesResponse",
|
||||||
status: parseInt(
|
status: parseInt(
|
||||||
@@ -256,13 +283,13 @@ function SetParameterValuesResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddObject(xml, methodRequest) {
|
function AddObject(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:AddObject");
|
const el = xml.node("cwmp:AddObject");
|
||||||
el.node("ObjectName").text(methodRequest.objectName);
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddObjectResponse(xml) {
|
function AddObjectResponse(xml): CpeSetResponse {
|
||||||
let instanceNumber, status;
|
let instanceNumber, status;
|
||||||
for (const c of xml.childNodes()) {
|
for (const c of xml.childNodes()) {
|
||||||
switch (c.name()) {
|
switch (c.name()) {
|
||||||
@@ -282,13 +309,13 @@ function AddObjectResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteObject(xml, methodRequest) {
|
function DeleteObject(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:DeleteObject");
|
const el = xml.node("cwmp:DeleteObject");
|
||||||
el.node("ObjectName").text(methodRequest.objectName);
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteObjectResponse(xml) {
|
function DeleteObjectResponse(xml): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "DeleteObjectResponse",
|
name: "DeleteObjectResponse",
|
||||||
status: parseInt(
|
status: parseInt(
|
||||||
@@ -300,28 +327,28 @@ function DeleteObjectResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Reboot(xml, methodRequest) {
|
function Reboot(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:Reboot");
|
const el = xml.node("cwmp:Reboot");
|
||||||
el.node("CommandKey").text(methodRequest.commandKey || "");
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function RebootResponse() {
|
function RebootResponse(): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "RebootResponse"
|
name: "RebootResponse"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function FactoryReset(xml) {
|
function FactoryReset(xml): void {
|
||||||
xml.node("cwmp:FactoryReset");
|
xml.node("cwmp:FactoryReset");
|
||||||
}
|
}
|
||||||
|
|
||||||
function FactoryResetResponse() {
|
function FactoryResetResponse(): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "FactoryResetResponse"
|
name: "FactoryResetResponse"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Download(xml, methodRequest) {
|
function Download(xml, methodRequest): void {
|
||||||
const el = xml.node("cwmp:Download");
|
const el = xml.node("cwmp:Download");
|
||||||
el.node("CommandKey").text(methodRequest.commandKey || "");
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
el.node("FileType").text(methodRequest.fileType);
|
el.node("FileType").text(methodRequest.fileType);
|
||||||
@@ -335,7 +362,7 @@ function Download(xml, methodRequest) {
|
|||||||
el.node("FailureURL").text(methodRequest.failureUrl || "");
|
el.node("FailureURL").text(methodRequest.failureUrl || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadResponse(xml) {
|
function DownloadResponse(xml): CpeSetResponse {
|
||||||
let status, startTime, completeTime;
|
let status, startTime, completeTime;
|
||||||
for (const c of xml.childNodes()) {
|
for (const c of xml.childNodes()) {
|
||||||
switch (c.name()) {
|
switch (c.name()) {
|
||||||
@@ -359,7 +386,7 @@ function DownloadResponse(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inform(xml) {
|
function Inform(xml): InformRequest {
|
||||||
let retryCount, evnt, parameterList;
|
let retryCount, evnt, parameterList;
|
||||||
const deviceId = {
|
const deviceId = {
|
||||||
Manufacturer: null,
|
Manufacturer: null,
|
||||||
@@ -397,18 +424,18 @@ function Inform(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function InformResponse(xml) {
|
function InformResponse(xml): void {
|
||||||
xml
|
xml
|
||||||
.node("cwmp:InformResponse")
|
.node("cwmp:InformResponse")
|
||||||
.node("MaxEnvelopes")
|
.node("MaxEnvelopes")
|
||||||
.text(1);
|
.text(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetRPCMethods() {
|
function GetRPCMethods(): AcsResponse {
|
||||||
return { name: "GetRPCMethods" };
|
return { name: "GetRPCMethods" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetRPCMethodsResponse(xml, methodResponse) {
|
function GetRPCMethodsResponse(xml, methodResponse): void {
|
||||||
const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList");
|
const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList");
|
||||||
el.attr({
|
el.attr({
|
||||||
"soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]`
|
"soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]`
|
||||||
@@ -417,7 +444,7 @@ function GetRPCMethodsResponse(xml, methodResponse) {
|
|||||||
for (const m of methodResponse.methodList) el.node("string").text(m);
|
for (const m of methodResponse.methodList) el.node("string").text(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TransferComplete(xml) {
|
function TransferComplete(xml): AcsResponse {
|
||||||
let commandKey, _faultStruct, startTime, completeTime;
|
let commandKey, _faultStruct, startTime, completeTime;
|
||||||
for (const c of xml.childNodes()) {
|
for (const c of xml.childNodes()) {
|
||||||
switch (c.name()) {
|
switch (c.name()) {
|
||||||
@@ -445,11 +472,11 @@ function TransferComplete(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function TransferCompleteResponse(xml) {
|
function TransferCompleteResponse(xml): void {
|
||||||
xml.node("cwmp:TransferCompleteResponse").text("");
|
xml.node("cwmp:TransferCompleteResponse").text("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequestDownload(xml) {
|
function RequestDownload(xml): CpeRequest {
|
||||||
return {
|
return {
|
||||||
name: "RequestDownload",
|
name: "RequestDownload",
|
||||||
fileType: xml
|
fileType: xml
|
||||||
@@ -459,12 +486,12 @@ function RequestDownload(xml) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequestDownloadResponse(xml) {
|
function RequestDownloadResponse(xml): void {
|
||||||
xml.node("cwmp:RequestDownloadResponse").text("");
|
xml.node("cwmp:RequestDownloadResponse").text("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function faultStruct(xml) {
|
function faultStruct(xml): FaultStruct {
|
||||||
let faultCode, faultString, setParameterValuesFault, pn, fc, fs;
|
let faultCode, faultString, setParameterValuesFault: SpvFault[], pn, fc, fs;
|
||||||
for (const c of xml.childNodes()) {
|
for (const c of xml.childNodes()) {
|
||||||
switch (c.name()) {
|
switch (c.name()) {
|
||||||
case "FaultCode":
|
case "FaultCode":
|
||||||
@@ -499,7 +526,7 @@ function faultStruct(xml) {
|
|||||||
return { faultCode, faultString, setParameterValuesFault };
|
return { faultCode, faultString, setParameterValuesFault };
|
||||||
}
|
}
|
||||||
|
|
||||||
function fault(xml) {
|
function fault(xml): CpeFault {
|
||||||
let faultCode, faultString, detail;
|
let faultCode, faultString, detail;
|
||||||
for (const c of xml.childNodes()) {
|
for (const c of xml.childNodes()) {
|
||||||
switch (c.name()) {
|
switch (c.name()) {
|
||||||
@@ -518,7 +545,18 @@ function fault(xml) {
|
|||||||
return { faultCode, faultString, detail };
|
return { faultCode, faultString, detail };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(data, cwmpVersion, warn) {
|
export function request(
|
||||||
|
data,
|
||||||
|
cwmpVersion,
|
||||||
|
warn
|
||||||
|
): {
|
||||||
|
id: string;
|
||||||
|
cwmpVersion: string;
|
||||||
|
sessionTimeout: number;
|
||||||
|
cpeRequest?: CpeRequest;
|
||||||
|
cpeFault?: CpeFault;
|
||||||
|
cpeResponse?: CpeResponse;
|
||||||
|
} {
|
||||||
warnings = warn;
|
warnings = warn;
|
||||||
|
|
||||||
const rpc = {
|
const rpc = {
|
||||||
@@ -526,12 +564,13 @@ export function request(data, cwmpVersion, warn) {
|
|||||||
cwmpVersion: cwmpVersion,
|
cwmpVersion: cwmpVersion,
|
||||||
sessionTimeout: null,
|
sessionTimeout: null,
|
||||||
cpeRequest: null,
|
cpeRequest: null,
|
||||||
cpeFault: null
|
cpeFault: null,
|
||||||
|
cpeResponse: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data.length) return rpc;
|
if (!data.length) return rpc;
|
||||||
|
|
||||||
const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS);
|
const xml = parseXml(data, LIBXMLJS_PARSE_OPTIONS);
|
||||||
|
|
||||||
let headerElement, bodyElement;
|
let headerElement, bodyElement;
|
||||||
|
|
||||||
@@ -589,7 +628,7 @@ export function request(data, cwmpVersion, warn) {
|
|||||||
rpc.cpeRequest = Inform(methodElement);
|
rpc.cpeRequest = Inform(methodElement);
|
||||||
break;
|
break;
|
||||||
case "GetRPCMethods":
|
case "GetRPCMethods":
|
||||||
rpc.cpeRequest = GetRPCMethods(methodElement);
|
rpc.cpeRequest = GetRPCMethods();
|
||||||
break;
|
break;
|
||||||
case "TransferComplete":
|
case "TransferComplete":
|
||||||
rpc.cpeRequest = TransferComplete(methodElement);
|
rpc.cpeRequest = TransferComplete(methodElement);
|
||||||
@@ -613,16 +652,16 @@ export function request(data, cwmpVersion, warn) {
|
|||||||
rpc.cpeResponse = DeleteObjectResponse(methodElement);
|
rpc.cpeResponse = DeleteObjectResponse(methodElement);
|
||||||
break;
|
break;
|
||||||
case "RebootResponse":
|
case "RebootResponse":
|
||||||
rpc.cpeResponse = RebootResponse(methodElement);
|
rpc.cpeResponse = RebootResponse();
|
||||||
break;
|
break;
|
||||||
case "FactoryResetResponse":
|
case "FactoryResetResponse":
|
||||||
rpc.cpeResponse = FactoryResetResponse(methodElement);
|
rpc.cpeResponse = FactoryResetResponse();
|
||||||
break;
|
break;
|
||||||
case "DownloadResponse":
|
case "DownloadResponse":
|
||||||
rpc.cpeResponse = DownloadResponse(methodElement);
|
rpc.cpeResponse = DownloadResponse(methodElement);
|
||||||
break;
|
break;
|
||||||
case "Fault":
|
case "Fault":
|
||||||
rpc.cpeFault = fault(methodElement, rpc.cwmpVersion);
|
rpc.cpeFault = fault(methodElement);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`8000 Method not supported ${methodName}`);
|
throw new Error(`8000 Method not supported ${methodName}`);
|
||||||
@@ -632,8 +671,8 @@ export function request(data, cwmpVersion, warn) {
|
|||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSoapEnv(cwmpVersion) {
|
function createSoapEnv(cwmpVersion: string): Element {
|
||||||
const xml = libxmljs.Document();
|
const xml = new Document();
|
||||||
const env = xml.node("soap-env:Envelope");
|
const env = xml.node("soap-env:Envelope");
|
||||||
|
|
||||||
for (const prefix of Object.keys(NAMESPACES[cwmpVersion]))
|
for (const prefix of Object.keys(NAMESPACES[cwmpVersion]))
|
||||||
@@ -642,20 +681,20 @@ function createSoapEnv(cwmpVersion) {
|
|||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function response(rpc) {
|
export function response(rpc): { code: number; headers: {}; 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: Buffer.allocUnsafe(0) };
|
if (!rpc) return { code: 204, headers: headers, data: "" };
|
||||||
|
|
||||||
const env = createSoapEnv(rpc.cwmpVersion);
|
const env = createSoapEnv(rpc.cwmpVersion);
|
||||||
const header = env.node("soap-env:Header");
|
const header = env.node("soap-env:Header");
|
||||||
header
|
header
|
||||||
.node("cwmp:ID")
|
.node("cwmp:ID")
|
||||||
.attr({
|
.attr({
|
||||||
"soap-env:mustUnderstand": 1
|
"soap-env:mustUnderstand": "1"
|
||||||
})
|
})
|
||||||
.text(rpc.id);
|
.text(rpc.id);
|
||||||
|
|
||||||
@@ -664,16 +703,16 @@ export function response(rpc) {
|
|||||||
if (rpc.acsResponse) {
|
if (rpc.acsResponse) {
|
||||||
switch (rpc.acsResponse.name) {
|
switch (rpc.acsResponse.name) {
|
||||||
case "InformResponse":
|
case "InformResponse":
|
||||||
InformResponse(body, rpc.acsResponse);
|
InformResponse(body);
|
||||||
break;
|
break;
|
||||||
case "GetRPCMethodsResponse":
|
case "GetRPCMethodsResponse":
|
||||||
GetRPCMethodsResponse(body, rpc.acsResponse);
|
GetRPCMethodsResponse(body, rpc.acsResponse);
|
||||||
break;
|
break;
|
||||||
case "TransferCompleteResponse":
|
case "TransferCompleteResponse":
|
||||||
TransferCompleteResponse(body, rpc.acsResponse);
|
TransferCompleteResponse(body);
|
||||||
break;
|
break;
|
||||||
case "RequestDownloadResponse":
|
case "RequestDownloadResponse":
|
||||||
RequestDownloadResponse(body, rpc.acsResponse);
|
RequestDownloadResponse(body);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
|
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
|
||||||
@@ -699,7 +738,7 @@ export function response(rpc) {
|
|||||||
Reboot(body, rpc.acsRequest);
|
Reboot(body, rpc.acsRequest);
|
||||||
break;
|
break;
|
||||||
case "FactoryReset":
|
case "FactoryReset":
|
||||||
FactoryReset(body, rpc.acsRequest);
|
FactoryReset(body);
|
||||||
break;
|
break;
|
||||||
case "Download":
|
case "Download":
|
||||||
Download(body, rpc.acsRequest);
|
Download(body, rpc.acsRequest);
|
||||||
+305
@@ -0,0 +1,305 @@
|
|||||||
|
import PathSet from "./path-set";
|
||||||
|
import VersionedMap from "./versioned-map";
|
||||||
|
import InstanceSet from "./instance-set";
|
||||||
|
import { IncomingMessage, ServerResponse } from "http";
|
||||||
|
|
||||||
|
export interface ParamPath extends Array<any> {
|
||||||
|
wildcard: number;
|
||||||
|
alias: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Expression = string | number | boolean | null | any[];
|
||||||
|
|
||||||
|
export interface Fault {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
detail?: {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
stack?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionFault extends Fault {
|
||||||
|
channel: string;
|
||||||
|
timestamp: number;
|
||||||
|
provisions: string[][];
|
||||||
|
retryNow?: boolean;
|
||||||
|
precondition?: boolean;
|
||||||
|
retries?: number;
|
||||||
|
expiry?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Attributes {
|
||||||
|
object?: [number, 1 | 0];
|
||||||
|
writable?: [number, 1 | 0];
|
||||||
|
value?: [number, [string | number | boolean, string]];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeTimestamps {
|
||||||
|
object?: number;
|
||||||
|
writable?: number;
|
||||||
|
value?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeValues {
|
||||||
|
object?: boolean;
|
||||||
|
writable?: boolean;
|
||||||
|
value?: [string | number | boolean, string?];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceData {
|
||||||
|
paths: PathSet;
|
||||||
|
timestamps: VersionedMap<ParamPath, number>;
|
||||||
|
attributes: VersionedMap<ParamPath, Attributes>;
|
||||||
|
loaded: Map<ParamPath, number>;
|
||||||
|
trackers: Map<ParamPath, { [name: string]: number }>;
|
||||||
|
changes: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VirtualParameterDeclaration = [
|
||||||
|
ParamPath,
|
||||||
|
{ path?: number; object?: number; writable?: number; value?: number }?,
|
||||||
|
{
|
||||||
|
path?: [number, number];
|
||||||
|
object?: boolean;
|
||||||
|
writable?: boolean;
|
||||||
|
value?: [string | number | boolean, string?];
|
||||||
|
}?
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface SyncState {
|
||||||
|
refreshAttributes: {
|
||||||
|
exist: Set<ParamPath>;
|
||||||
|
object: Set<ParamPath>;
|
||||||
|
writable: Set<ParamPath>;
|
||||||
|
value: Set<ParamPath>;
|
||||||
|
};
|
||||||
|
spv: Map<ParamPath, [string | number | boolean, string]>;
|
||||||
|
gpn: Set<ParamPath>;
|
||||||
|
gpnPatterns: Map<ParamPath, number>;
|
||||||
|
tags: Map<ParamPath, [string | number | boolean, string]>;
|
||||||
|
virtualParameterDeclarations: VirtualParameterDeclaration[][];
|
||||||
|
instancesToDelete: Map<ParamPath, Set<ParamPath>>;
|
||||||
|
instancesToCreate: Map<ParamPath, InstanceSet>;
|
||||||
|
downloadsToDelete: Set<ParamPath>;
|
||||||
|
downloadsToCreate: InstanceSet;
|
||||||
|
downloadsValues: Map<ParamPath, string | number>;
|
||||||
|
downloadsDownload: Map<ParamPath, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionContext {
|
||||||
|
sessionId?: string;
|
||||||
|
timestamp: number;
|
||||||
|
deviceId: string;
|
||||||
|
deviceData: DeviceData;
|
||||||
|
cwmpVersion: string;
|
||||||
|
timeout: number;
|
||||||
|
provisions: any[];
|
||||||
|
channels: { [channel: string]: number };
|
||||||
|
virtualParameters: any[];
|
||||||
|
revisions: number[];
|
||||||
|
rpcCount: number;
|
||||||
|
iteration: number;
|
||||||
|
cycle: number;
|
||||||
|
extensionsCache: any;
|
||||||
|
declarations: any[];
|
||||||
|
faults?: { [channel: string]: SessionFault };
|
||||||
|
retries?: { [channel: string]: number };
|
||||||
|
cacheSnapshot?: string;
|
||||||
|
configContext?: { [name: string]: any };
|
||||||
|
httpResponse?: ServerResponse;
|
||||||
|
httpRequest?: IncomingMessage;
|
||||||
|
faultsTouched?: { [channel: string]: boolean };
|
||||||
|
presetCycles?: number;
|
||||||
|
toLoad?: Map<ParamPath, number>;
|
||||||
|
new?: boolean;
|
||||||
|
debug?: boolean;
|
||||||
|
tasks?: Task[];
|
||||||
|
operations?: { [commandKey: string]: Operation };
|
||||||
|
cacheUntil?: number;
|
||||||
|
syncState?: SyncState;
|
||||||
|
lastActivity?: number;
|
||||||
|
rpcRequest?: AcsRequest;
|
||||||
|
operationsTouched?: { [commandKey: string]: 1 | 0 };
|
||||||
|
provisionsRet?: any[];
|
||||||
|
doneTasks?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Task {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
parameterNames?: string[];
|
||||||
|
parameterValues?: [string, string | number | boolean, string?][];
|
||||||
|
objectName?: string;
|
||||||
|
fileType?: string;
|
||||||
|
fileName?: string;
|
||||||
|
targetFileName?: string;
|
||||||
|
expiry?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Operation {
|
||||||
|
name: string;
|
||||||
|
timestamp: number;
|
||||||
|
provisions: string[][];
|
||||||
|
channels: { [channel: string]: number };
|
||||||
|
retries: { [channel: string]: number };
|
||||||
|
args: {
|
||||||
|
instance: string;
|
||||||
|
fileType: string;
|
||||||
|
fileName: string;
|
||||||
|
targetFileName: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AcsRequest {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetAcsRequest extends AcsRequest {
|
||||||
|
name: "GetParameterNames" | "GetParameterValues";
|
||||||
|
objectName?: string;
|
||||||
|
parameterNames?: string[];
|
||||||
|
parameterPath?: string;
|
||||||
|
nextLevel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetAcsRequest extends AcsRequest {
|
||||||
|
name:
|
||||||
|
| "SetParameterValues"
|
||||||
|
| "AddObject"
|
||||||
|
| "DeleteObject"
|
||||||
|
| "FactoryReset"
|
||||||
|
| "Reboot"
|
||||||
|
| "Download";
|
||||||
|
parameterList?: [string, string | number | boolean, string][];
|
||||||
|
instanceValues?: { [param: string]: string | number | boolean };
|
||||||
|
objectName?: string;
|
||||||
|
next?: string;
|
||||||
|
DATETIME_MILLISECONDS?: boolean;
|
||||||
|
BOOLEAN_LITERAL?: boolean;
|
||||||
|
commandKey?: string;
|
||||||
|
instance?: string;
|
||||||
|
fileType?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
url?: string;
|
||||||
|
fileName?: string;
|
||||||
|
targetFileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpeResponse {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpvFault {
|
||||||
|
parameterName: string;
|
||||||
|
faultCode: string;
|
||||||
|
faultString: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FaultStruct {
|
||||||
|
faultCode: string;
|
||||||
|
faultString: string;
|
||||||
|
setParameterValuesFault?: SpvFault[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpeFault {
|
||||||
|
faultCode: string;
|
||||||
|
faultString: string;
|
||||||
|
detail?: FaultStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpeGetResponse extends CpeResponse {
|
||||||
|
name: "GetParameterNamesResponse" | "GetParameterValuesResponse";
|
||||||
|
parameterList?:
|
||||||
|
| [string, boolean][]
|
||||||
|
| [string, string | number | boolean, string][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpeSetResponse extends CpeResponse {
|
||||||
|
name:
|
||||||
|
| "SetParameterValuesResponse"
|
||||||
|
| "AddObjectResponse"
|
||||||
|
| "DeleteObjectResponse"
|
||||||
|
| "RebootResponse"
|
||||||
|
| "FactoryResetResponse"
|
||||||
|
| "DownloadResponse";
|
||||||
|
status?: number;
|
||||||
|
instanceNumber?: string;
|
||||||
|
startTime?: number;
|
||||||
|
completeTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpeRequest {
|
||||||
|
name: string;
|
||||||
|
fileType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InformRequest extends CpeRequest {
|
||||||
|
name: "Inform";
|
||||||
|
deviceId: {
|
||||||
|
Manufacturer: string;
|
||||||
|
OUI: string;
|
||||||
|
ProductClass?: string;
|
||||||
|
SerialNumber: string;
|
||||||
|
};
|
||||||
|
event: string[];
|
||||||
|
retryCount: number;
|
||||||
|
parameterList: [string, string | number | boolean, string][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AcsResponse {
|
||||||
|
name: string;
|
||||||
|
commandKey?: string;
|
||||||
|
faultStruct?: FaultStruct;
|
||||||
|
startTime?: number;
|
||||||
|
completeTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryOptions {
|
||||||
|
projection?: any;
|
||||||
|
skip?: number;
|
||||||
|
limit?: number;
|
||||||
|
sort?: {
|
||||||
|
[param: string]: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Declaration {
|
||||||
|
path: ParamPath;
|
||||||
|
pathGet: number;
|
||||||
|
pathSet?: [number, number];
|
||||||
|
attrGet?: { object?: number; writable?: number; value?: number };
|
||||||
|
attrSet?: {
|
||||||
|
object?: boolean;
|
||||||
|
writable?: boolean;
|
||||||
|
value?: [string | number | boolean, string?];
|
||||||
|
};
|
||||||
|
defer: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Clear = [
|
||||||
|
ParamPath,
|
||||||
|
number,
|
||||||
|
{ object?: number; writable?: number; value?: number }?,
|
||||||
|
number?
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface Preset {
|
||||||
|
name: string;
|
||||||
|
channel: string;
|
||||||
|
schedule?: { md5: string; duration: number; schedule: any };
|
||||||
|
events?: { [event: string]: boolean };
|
||||||
|
precondition?: {};
|
||||||
|
provisions: string[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionSet = {
|
||||||
|
[resource: string]: {
|
||||||
|
access: number;
|
||||||
|
validate?: {
|
||||||
|
[validator: string]: boolean;
|
||||||
|
};
|
||||||
|
filter: Expression;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
+15
-9
@@ -1,5 +1,3 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { Z_SYNC_FLUSH } from "zlib";
|
import { Z_SYNC_FLUSH } from "zlib";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
@@ -14,14 +12,21 @@ import Authorizer from "./common/authorizer";
|
|||||||
import { parse } from "./common/expression-parser";
|
import { parse } from "./common/expression-parser";
|
||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import * as localCache from "./local-cache";
|
import * as localCache from "./local-cache";
|
||||||
|
import { PermissionSet } from "./types";
|
||||||
|
|
||||||
|
declare module "koa" {
|
||||||
|
interface Request {
|
||||||
|
body: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const koa = new Koa();
|
const koa = new Koa();
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
const JWT_SECRET = config.get("UI_JWT_SECRET");
|
const JWT_SECRET = "" + config.get("UI_JWT_SECRET");
|
||||||
const JWT_COOKIE = "genieacs-ui-jwt";
|
const JWT_COOKIE = "genieacs-ui-jwt";
|
||||||
|
|
||||||
function getPermissionSets(ctx) {
|
function getPermissionSets(ctx): PermissionSet[] {
|
||||||
const allPermissions = localCache.getPermissionsConfig(
|
const allPermissions = localCache.getPermissionsConfig(
|
||||||
ctx.state.configSnapshot
|
ctx.state.configSnapshot
|
||||||
);
|
);
|
||||||
@@ -50,9 +55,9 @@ function getPermissionSets(ctx) {
|
|||||||
return permissionSets;
|
return permissionSets;
|
||||||
}
|
}
|
||||||
|
|
||||||
function authSimple(ctx, username, password) {
|
function authSimple(ctx, username, password): Promise<string[]> {
|
||||||
const authConfig = localCache.getAuthConfig(ctx.state.configSnapshot);
|
const authConfig = localCache.getAuthConfig(ctx.state.configSnapshot);
|
||||||
const user = authConfig.simple.users[username];
|
const user = authConfig["simple"].users[username];
|
||||||
if (user && user.password === password) {
|
if (user && user.password === password) {
|
||||||
const roles = user.roles.split(",").map(s => s.trim());
|
const roles = user.roles.split(",").map(s => s.trim());
|
||||||
return Promise.resolve(roles);
|
return Promise.resolve(roles);
|
||||||
@@ -100,10 +105,11 @@ router.post("/login", async ctx => {
|
|||||||
const log = {
|
const log = {
|
||||||
message: "Log in",
|
message: "Log in",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
username: username
|
username: username,
|
||||||
|
method: null
|
||||||
};
|
};
|
||||||
|
|
||||||
function success(roles, method) {
|
function success(roles, method): void {
|
||||||
log.method = method;
|
log.method = method;
|
||||||
const token = jwt.sign({ username, roles }, JWT_SECRET);
|
const token = jwt.sign({ username, roles }, JWT_SECRET);
|
||||||
ctx.cookies.set(JWT_COOKIE, token);
|
ctx.cookies.set(JWT_COOKIE, token);
|
||||||
@@ -111,7 +117,7 @@ router.post("/login", async ctx => {
|
|||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
function failure() {
|
function failure(): void {
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
ctx.body = "Incorrect username or password";
|
ctx.body = "Incorrect username or password";
|
||||||
log.message += " failed";
|
log.message += " failed";
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
"use strict";
|
import { exec } from "child_process";
|
||||||
|
import { ObjectID } from "mongodb";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import { del } from "../cache";
|
import { del } from "../cache";
|
||||||
import { ObjectID } from "mongodb";
|
|
||||||
import { exec } from "child_process";
|
|
||||||
import {
|
import {
|
||||||
insertTasks,
|
insertTasks,
|
||||||
watchTask,
|
watchTask,
|
||||||
@@ -11,7 +9,7 @@ import {
|
|||||||
deleteDevice
|
deleteDevice
|
||||||
} from "../api-functions";
|
} from "../api-functions";
|
||||||
|
|
||||||
function deleteFault(id) {
|
function deleteFault(id): Promise<void> {
|
||||||
const deviceId = id.split(":", 1)[0];
|
const deviceId = id.split(":", 1)[0];
|
||||||
const channel = id.slice(deviceId.length + 1);
|
const channel = id.slice(deviceId.length + 1);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -30,7 +28,7 @@ function deleteFault(id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteResource(resource, id) {
|
export function deleteResource(resource, id): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
@@ -80,7 +78,11 @@ export function deleteResource(resource, id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postTasks(deviceId, tasks, timeout) {
|
export function postTasks(
|
||||||
|
deviceId,
|
||||||
|
tasks,
|
||||||
|
timeout
|
||||||
|
): Promise<{ connectionRequest: string; tasks: any[] }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
for (const task of tasks) task.device = deviceId;
|
for (const task of tasks) task.device = deviceId;
|
||||||
|
|
||||||
@@ -139,7 +141,17 @@ export function postTasks(deviceId, tasks, timeout) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ping(host) {
|
interface PingResponse {
|
||||||
|
packetsTransmitted: number;
|
||||||
|
packetsReceived: number;
|
||||||
|
packetLoss: string;
|
||||||
|
min: number;
|
||||||
|
avg: number;
|
||||||
|
max: number;
|
||||||
|
mdev: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ping(host): Promise<PingResponse> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(`ping -w 1 -i 0.2 -c 3 ${host}`, (err, stdout) => {
|
exec(`ping -w 1 -i 0.2 -c 3 ${host}`, (err, stdout) => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
@@ -162,7 +174,7 @@ export function ping(host) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putResource(resource, id, data) {
|
export function putResource(resource, id, data): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
"use strict";
|
import * as stream from "stream";
|
||||||
|
|
||||||
import stream from "stream";
|
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as apiFunctions from "./api-functions";
|
import * as apiFunctions from "./api-functions";
|
||||||
@@ -8,11 +6,12 @@ import { evaluate, and, extractParams } from "../common/expression";
|
|||||||
import { parse } from "../common/expression-parser";
|
import { parse } from "../common/expression-parser";
|
||||||
import * as logger from "../logger";
|
import * as logger from "../logger";
|
||||||
import { getConfig } from "../local-cache";
|
import { getConfig } from "../local-cache";
|
||||||
|
import { QueryOptions, Expression } from "../types";
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
function logUnauthorizedWarning(log) {
|
function logUnauthorizedWarning(log): void {
|
||||||
log.message += " not authorized";
|
log.message += " not authorized";
|
||||||
logger.accessWarn(log);
|
logger.accessWarn(log);
|
||||||
}
|
}
|
||||||
@@ -91,13 +90,14 @@ router.get(`/devices/:id.csv`, async ctx => {
|
|||||||
|
|
||||||
for (const [resource, flags] of Object.entries(resources)) {
|
for (const [resource, flags] of Object.entries(resources)) {
|
||||||
router.head(`/${resource}`, async (ctx, next) => {
|
router.head(`/${resource}`, async (ctx, next) => {
|
||||||
let filter = true;
|
let filter: Expression = true;
|
||||||
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
||||||
|
|
||||||
const log = {
|
const log = {
|
||||||
message: `Count ${resource}`,
|
message: `Count ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
filter: ctx.request.query.filter
|
filter: ctx.request.query.filter,
|
||||||
|
count: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!ctx.state.authorizer.hasAccess(resource, 1)) {
|
if (!ctx.state.authorizer.hasAccess(resource, 1)) {
|
||||||
@@ -115,7 +115,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
|
|
||||||
const count = await db.count(resource, filter);
|
const count = await db.count(resource, filter);
|
||||||
|
|
||||||
ctx.set("X-Total-Count", count);
|
ctx.set("X-Total-Count", `${count}`);
|
||||||
ctx.body = "";
|
ctx.body = "";
|
||||||
|
|
||||||
log.count = count;
|
log.count = count;
|
||||||
@@ -123,8 +123,8 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get(`/${resource}`, async (ctx, next) => {
|
router.get(`/${resource}`, async (ctx, next) => {
|
||||||
const options = {};
|
const options: QueryOptions = {};
|
||||||
let filter = true;
|
let filter: Expression = true;
|
||||||
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
||||||
if (ctx.request.query.limit) options.limit = +ctx.request.query.limit;
|
if (ctx.request.query.limit) options.limit = +ctx.request.query.limit;
|
||||||
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
|
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
|
||||||
@@ -174,8 +174,8 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
|
|
||||||
// CSV download
|
// CSV download
|
||||||
router.get(`/${resource}.csv`, async (ctx, next) => {
|
router.get(`/${resource}.csv`, async (ctx, next) => {
|
||||||
const options = { projection: {} };
|
const options: QueryOptions = { projection: {} };
|
||||||
let filter = true;
|
let filter: Expression = true;
|
||||||
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
if (ctx.request.query.filter) filter = parse(ctx.request.query.filter);
|
||||||
if (ctx.request.query.limit) options.limit = +ctx.request.query.limit;
|
if (ctx.request.query.limit) options.limit = +ctx.request.query.limit;
|
||||||
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
|
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
|
||||||
@@ -356,9 +356,9 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
return void (ctx.status = 403);
|
return void (ctx.status = 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const err = await apiFunctions.putResource(resource, id, obj);
|
try {
|
||||||
|
await apiFunctions.putResource(resource, id, obj);
|
||||||
if (err) {
|
} catch (err) {
|
||||||
log.message += " failed";
|
log.message += " failed";
|
||||||
logger.accessWarn(log);
|
logger.accessWarn(log);
|
||||||
ctx.body = err;
|
ctx.body = err;
|
||||||
@@ -379,7 +379,8 @@ router.put("/files/:id", async (ctx, next) => {
|
|||||||
const log = {
|
const log = {
|
||||||
message: `Upload ${resource}`,
|
message: `Upload ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
id: id
|
id: id,
|
||||||
|
metadata: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const authorizer = ctx.state.authorizer;
|
const authorizer = ctx.state.authorizer;
|
||||||
@@ -412,7 +413,8 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
|
|||||||
const log = {
|
const log = {
|
||||||
message: "Commit tasks",
|
message: "Commit tasks",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
deviceId: ctx.params.id
|
deviceId: ctx.params.id,
|
||||||
|
tasks: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const authorizer = ctx.state.authorizer;
|
const authorizer = ctx.state.authorizer;
|
||||||
+29
-25
@@ -1,10 +1,9 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { MongoClient, GridFSBucket } from "mongodb";
|
import { MongoClient, GridFSBucket } from "mongodb";
|
||||||
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 { preProcessPreset } from "./mongodb-functions";
|
import { preProcessPreset } from "./mongodb-functions";
|
||||||
|
import { QueryOptions } from "../types";
|
||||||
|
|
||||||
const CACHE_TTL = 300000;
|
const CACHE_TTL = 300000;
|
||||||
|
|
||||||
@@ -14,17 +13,17 @@ const RESOURCE_COLLECTION = {
|
|||||||
files: "fs.files"
|
files: "fs.files"
|
||||||
};
|
};
|
||||||
|
|
||||||
function ensureIndexes(client) {
|
function ensureIndexes(client): void {
|
||||||
client
|
client
|
||||||
.db()
|
.db()
|
||||||
.collection("cache")
|
.collection("cache")
|
||||||
.createIndex({ expire: 1 }, { expireAfterSeconds: 0 });
|
.createIndex({ expire: 1 }, { expireAfterSeconds: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClient() {
|
function getClient(): Promise<MongoClient> {
|
||||||
if (!_clientPromise) {
|
if (!_clientPromise) {
|
||||||
_clientPromise = new Promise((resolve, reject) => {
|
_clientPromise = new Promise((resolve, reject) => {
|
||||||
const CONNECTION_URL = config.get("MONGODB_CONNECTION_URL");
|
const CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL");
|
||||||
MongoClient.connect(
|
MongoClient.connect(
|
||||||
CONNECTION_URL,
|
CONNECTION_URL,
|
||||||
{ useNewUrlParser: true },
|
{ useNewUrlParser: true },
|
||||||
@@ -40,14 +39,14 @@ function getClient() {
|
|||||||
return _clientPromise;
|
return _clientPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cache(key, valueGetter, ttl) {
|
export function cache<T>(key, valueGetter: () => Promise<T>, ttl): 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));
|
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);
|
||||||
@@ -73,7 +72,12 @@ export function cache(key, valueGetter, ttl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function query(resource, filter, options, callback) {
|
export function query(
|
||||||
|
resource,
|
||||||
|
filter,
|
||||||
|
options?: QueryOptions,
|
||||||
|
callback?: (doc: any) => void
|
||||||
|
): Promise<any[]> {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
let q;
|
let q;
|
||||||
filter = expression.evaluate(filter, null, Date.now());
|
filter = expression.evaluate(filter, null, Date.now());
|
||||||
@@ -159,7 +163,7 @@ export function query(resource, filter, options, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function count(resource, filter) {
|
export function count(resource, filter): Promise<number> {
|
||||||
let q;
|
let q;
|
||||||
filter = expression.evaluate(filter, null, Date.now());
|
filter = expression.evaluate(filter, null, Date.now());
|
||||||
|
|
||||||
@@ -188,7 +192,7 @@ export function count(resource, filter) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDeviceTags(deviceId, tags) {
|
export function updateDeviceTags(deviceId, tags): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const add = [];
|
const add = [];
|
||||||
const pull = [];
|
const pull = [];
|
||||||
@@ -213,7 +217,7 @@ export function updateDeviceTags(deviceId, tags) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function putResource(resource, id, object) {
|
function putResource(resource, id, object): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then(client => {
|
.then(client => {
|
||||||
@@ -229,7 +233,7 @@ function putResource(resource, id, object) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteResource(resource, id) {
|
function deleteResource(resource, id): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then(client => {
|
.then(client => {
|
||||||
@@ -245,40 +249,40 @@ function deleteResource(resource, id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putPreset(id, object) {
|
export function putPreset(id, object): Promise<void> {
|
||||||
object = preProcessPreset(object);
|
object = preProcessPreset(object);
|
||||||
return putResource("presets", id, object);
|
return putResource("presets", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deletePreset(id) {
|
export function deletePreset(id): Promise<void> {
|
||||||
return deleteResource("presets", id);
|
return deleteResource("presets", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putProvision(id, object) {
|
export function putProvision(id, object): Promise<void> {
|
||||||
return putResource("provisions", id, object);
|
return putResource("provisions", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteProvision(id) {
|
export function deleteProvision(id): Promise<void> {
|
||||||
return deleteResource("provisions", id);
|
return deleteResource("provisions", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putVirtualParameter(id, object) {
|
export function putVirtualParameter(id, object): Promise<void> {
|
||||||
return putResource("virtualParameters", id, object);
|
return putResource("virtualParameters", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteVirtualParameter(id) {
|
export function deleteVirtualParameter(id): Promise<void> {
|
||||||
return deleteResource("virtualParameters", id);
|
return deleteResource("virtualParameters", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putConfig(id, object) {
|
export function putConfig(id, object): Promise<void> {
|
||||||
return putResource("config", id, object);
|
return putResource("config", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteConfig(id) {
|
export function deleteConfig(id): Promise<void> {
|
||||||
return deleteResource("config", id);
|
return deleteResource("config", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putFile(filename, metadata, contentStream) {
|
export function putFile(filename, metadata, contentStream): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then(client => {
|
.then(client => {
|
||||||
@@ -295,7 +299,7 @@ export function putFile(filename, metadata, contentStream) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFile(filename) {
|
export function deleteFile(filename): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then(client => {
|
.then(client => {
|
||||||
@@ -309,15 +313,15 @@ export function deleteFile(filename) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFault(id) {
|
export function deleteFault(id): Promise<void> {
|
||||||
return deleteResource("faults", id);
|
return deleteResource("faults", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteTask(id) {
|
export function deleteTask(id): Promise<void> {
|
||||||
return deleteResource("tasks", id);
|
return deleteResource("tasks", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disconnect() {
|
export function disconnect(): void {
|
||||||
if (_clientPromise) {
|
if (_clientPromise) {
|
||||||
_clientPromise.then(client => {
|
_clientPromise.then(client => {
|
||||||
client.close();
|
client.close();
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
"use strict";
|
|
||||||
import { ObjectID } from "mongodb";
|
import { ObjectID } from "mongodb";
|
||||||
import { map, parse, stringify } from "../common/expression-parser";
|
import { map, parse, stringify } from "../common/expression-parser";
|
||||||
import { likePatternToRegExp, evaluate } from "../common/expression";
|
import { likePatternToRegExp, evaluate } from "../common/expression";
|
||||||
|
import { Expression, Fault, Task } from "../types";
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
export function processDeviceFilter(filter) {
|
export function processDeviceFilter(filter): Expression {
|
||||||
return map(filter, exp => {
|
return map(filter, exp => {
|
||||||
if (!isArray(exp)) return exp;
|
if (!isArray(exp)) return exp;
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export function processDeviceFilter(filter) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (typeof e[2] === "number") {
|
if (typeof e[2] === "number") {
|
||||||
const alt = e.slice();
|
const alt = (e as any[]).slice();
|
||||||
alt[2] = new Date(e[2]);
|
alt[2] = new Date(e[2]);
|
||||||
e = ["OR", e, alt];
|
e = ["OR", e, alt];
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export function processDeviceFilter(filter) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processTasksFilter(filter) {
|
export function processTasksFilter(filter): 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])) {
|
||||||
@@ -72,7 +72,7 @@ export function processTasksFilter(filter) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFaultsFilter(filter) {
|
export function processFaultsFilter(filter): 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])) {
|
||||||
@@ -86,7 +86,7 @@ export function processFaultsFilter(filter) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterToMongoQuery(exp) {
|
export function filterToMongoQuery(exp: Expression): {} {
|
||||||
const ops = {
|
const ops = {
|
||||||
OR: 0,
|
OR: 0,
|
||||||
AND: 0,
|
AND: 0,
|
||||||
@@ -103,7 +103,7 @@ export function filterToMongoQuery(exp) {
|
|||||||
"IS NOT NULL": 1
|
"IS NOT NULL": 1
|
||||||
};
|
};
|
||||||
|
|
||||||
function recursive(filter, negate, res = {}) {
|
function recursive(filter, negate, res = {}): {} {
|
||||||
const op = filter[0];
|
const op = filter[0];
|
||||||
|
|
||||||
if (ops[op] === 0) {
|
if (ops[op] === 0) {
|
||||||
@@ -219,7 +219,7 @@ export function filterToMongoQuery(exp) {
|
|||||||
return recursive(_exp, false);
|
return recursive(_exp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processDeviceProjection(projection) {
|
export function processDeviceProjection(projection: {}): {} {
|
||||||
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)) {
|
||||||
@@ -245,7 +245,7 @@ export function processDeviceProjection(projection) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processDeviceSort(sort) {
|
export function processDeviceSort(sort: {}): {} {
|
||||||
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)) {
|
||||||
@@ -261,13 +261,24 @@ export function processDeviceSort(sort) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDate(d) {
|
function parseDate(d): number | string {
|
||||||
const n = +d;
|
const n = +d;
|
||||||
return isNaN(n) ? "" + d : n;
|
return isNaN(n) ? "" + d : n;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenDevice(device) {
|
interface FlatDevice {
|
||||||
function recursive(input, root, output, timestamp) {
|
[param: string]: {
|
||||||
|
object: boolean;
|
||||||
|
objectTimestamp: number;
|
||||||
|
writable: boolean;
|
||||||
|
writableTimestamp: number;
|
||||||
|
value: [string | number | boolean, string];
|
||||||
|
valueTimestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenDevice(device): FlatDevice {
|
||||||
|
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") {
|
||||||
@@ -356,7 +367,7 @@ export function flattenDevice(device) {
|
|||||||
objectTimestamp: timestamp
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const t of tree) {
|
for (const t of tree as string[]) {
|
||||||
output[`Tags.${t}`] = {
|
output[`Tags.${t}`] = {
|
||||||
value: [true, "xsd:boolean"],
|
value: [true, "xsd:boolean"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
@@ -410,14 +421,14 @@ export function flattenDevice(device) {
|
|||||||
return newDevice;
|
return newDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenFault(fault) {
|
export function flattenFault(fault): Fault {
|
||||||
const f = Object.assign({}, fault);
|
const f = Object.assign({}, 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenTask(task) {
|
export function flattenTask(task): Task {
|
||||||
const t = Object.assign({}, task);
|
const t = Object.assign({}, task);
|
||||||
t._id = "" + t._id;
|
t._id = "" + t._id;
|
||||||
if (t.timestamp) t.timestamp = +t.timestamp;
|
if (t.timestamp) t.timestamp = +t.timestamp;
|
||||||
@@ -425,9 +436,9 @@ export function flattenTask(task) {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mongoQueryToFilter(query) {
|
function mongoQueryToFilter(query): Expression {
|
||||||
const expressions = [];
|
const expressions = [];
|
||||||
function recursive(_query) {
|
function recursive(_query): void {
|
||||||
for (const [k, v] of Object.entries(_query)) {
|
for (const [k, v] of Object.entries(_query)) {
|
||||||
if (k[0] === "$") {
|
if (k[0] === "$") {
|
||||||
if (k === "$and") for (const vv of Object.values(v)) recursive(vv);
|
if (k === "$and") for (const vv of Object.values(v)) recursive(vv);
|
||||||
@@ -492,7 +503,7 @@ function mongoQueryToFilter(query) {
|
|||||||
return expressions;
|
return expressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenPreset(preset) {
|
export function flattenPreset(preset): {} {
|
||||||
const p = Object.assign({}, preset);
|
const p = Object.assign({}, preset);
|
||||||
if (p.precondition) {
|
if (p.precondition) {
|
||||||
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
|
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
|
||||||
@@ -521,7 +532,7 @@ export function flattenPreset(preset) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenFile(file) {
|
export function flattenFile(file): {} {
|
||||||
const f = {};
|
const f = {};
|
||||||
f["_id"] = file["_id"];
|
f["_id"] = file["_id"];
|
||||||
if (file.metadata) {
|
if (file.metadata) {
|
||||||
@@ -533,7 +544,7 @@ export function flattenFile(file) {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function preProcessPreset(data) {
|
export function preProcessPreset(data): {} {
|
||||||
const preset = Object.assign({}, data);
|
const preset = Object.assign({}, data);
|
||||||
preset.precondition = preset.precondition
|
preset.precondition = preset.precondition
|
||||||
? filterToMongoQuery(parse(preset.precondition))
|
? filterToMongoQuery(parse(preset.precondition))
|
||||||
@@ -561,7 +572,8 @@ export function preProcessPreset(data) {
|
|||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
type: "provision",
|
type: "provision",
|
||||||
name: preset.provision
|
name: preset.provision,
|
||||||
|
args: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preset.provisionArgs)
|
if (preset.provisionArgs)
|
||||||
@@ -16,44 +16,53 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const NONEXISTENT = new Object();
|
const NONEXISTENT = Symbol();
|
||||||
const UNDEFINED = undefined;
|
const UNDEFINED = undefined;
|
||||||
|
|
||||||
export default class VersionedMap {
|
interface Revisions<V> {
|
||||||
constructor() {
|
[rev: number]: V;
|
||||||
|
delete?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VersionedMap<K, V> {
|
||||||
|
private _sizeDiff: number[];
|
||||||
|
private _revision: number;
|
||||||
|
private map: Map<K, (V | symbol)[]>;
|
||||||
|
public dirty: number;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
this._sizeDiff = [0];
|
this._sizeDiff = [0];
|
||||||
this._revision = 0;
|
this._revision = 0;
|
||||||
this.map = new Map();
|
this.map = new Map();
|
||||||
this.dirty = 0;
|
this.dirty = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get size() {
|
public get size(): number {
|
||||||
return this.map.size + this._sizeDiff[this.revision];
|
return this.map.size + this._sizeDiff[this.revision];
|
||||||
}
|
}
|
||||||
|
|
||||||
get revision() {
|
public get revision(): number {
|
||||||
return this._revision;
|
return this._revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
set revision(rev) {
|
public set revision(rev) {
|
||||||
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];
|
||||||
|
|
||||||
this._revision = rev;
|
this._revision = rev;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key, rev = this._revision) {
|
public get(key: K, rev = this._revision): V {
|
||||||
const revisions = this.map.get(key);
|
const revisions = this.map.get(key);
|
||||||
if (!revisions) return UNDEFINED;
|
if (!revisions) return UNDEFINED;
|
||||||
|
|
||||||
const v = revisions[Math.min(revisions.length - 1, rev)];
|
const v = revisions[Math.min(revisions.length - 1, rev)];
|
||||||
if (v === NONEXISTENT) return UNDEFINED;
|
if (v === NONEXISTENT) return UNDEFINED;
|
||||||
return v;
|
return v as V;
|
||||||
}
|
}
|
||||||
|
|
||||||
has(key, rev = this._revision) {
|
public has(key: K, rev = this._revision): boolean {
|
||||||
const revisions = this.map.get(key);
|
const revisions = this.map.get(key);
|
||||||
if (!revisions) return false;
|
if (!revisions) return false;
|
||||||
|
|
||||||
@@ -62,7 +71,7 @@ export default class VersionedMap {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value, rev = this._revision) {
|
public set(key: K, value: V, rev = this._revision): this {
|
||||||
let revisions = this.map.get(key);
|
let revisions = this.map.get(key);
|
||||||
if (!revisions) {
|
if (!revisions) {
|
||||||
this.dirty |= 1 << rev;
|
this.dirty |= 1 << rev;
|
||||||
@@ -72,7 +81,7 @@ export default class VersionedMap {
|
|||||||
for (let i = 0; i < rev; ++i) revisions[i] = NONEXISTENT;
|
for (let i = 0; i < rev; ++i) revisions[i] = NONEXISTENT;
|
||||||
revisions[rev] = value;
|
revisions[rev] = value;
|
||||||
this.map.set(key, revisions);
|
this.map.set(key, revisions);
|
||||||
return true;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't modify old revisions
|
// Can't modify old revisions
|
||||||
@@ -86,10 +95,10 @@ export default class VersionedMap {
|
|||||||
for (let i = revisions.length; i < rev; ++i) revisions[i] = old;
|
for (let i = revisions.length; i < rev; ++i) revisions[i] = old;
|
||||||
revisions[rev] = value;
|
revisions[rev] = value;
|
||||||
|
|
||||||
return true;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(key, rev = this._revision) {
|
public delete(key: K, rev = this._revision): boolean {
|
||||||
const revisions = this.map.get(key);
|
const revisions = this.map.get(key);
|
||||||
if (!revisions) return false;
|
if (!revisions) return false;
|
||||||
|
|
||||||
@@ -108,24 +117,24 @@ export default class VersionedMap {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRevisions(key) {
|
public getRevisions(key: K): Revisions<V> {
|
||||||
const revisions = this.map.get(key);
|
const revisions = this.map.get(key);
|
||||||
if (!revisions) return null;
|
if (!revisions) return null;
|
||||||
|
|
||||||
const res = {};
|
const res: Revisions<V> = {};
|
||||||
|
|
||||||
let prev = NONEXISTENT;
|
let prev: V | symbol = NONEXISTENT;
|
||||||
for (const [i, v] of revisions.entries()) {
|
for (const [i, v] of revisions.entries()) {
|
||||||
if (v === prev) continue;
|
if (v === prev) continue;
|
||||||
if (v === NONEXISTENT) res.delete |= 1 << i;
|
if (v === NONEXISTENT) res.delete |= 1 << i;
|
||||||
else res[i] = v;
|
else res[i] = v as V;
|
||||||
prev = v;
|
prev = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
setRevisions(key, revisionsObj) {
|
public setRevisions(key: K, revisionsObj: Revisions<V>): void {
|
||||||
const del = revisionsObj.delete || 0;
|
const del = revisionsObj.delete || 0;
|
||||||
const maxRev = Object.keys(revisionsObj).reduce(
|
const maxRev = Object.keys(revisionsObj).reduce(
|
||||||
(acc, cur) => (+cur > acc ? +cur : acc),
|
(acc, cur) => (+cur > acc ? +cur : acc),
|
||||||
@@ -134,7 +143,7 @@ export default class VersionedMap {
|
|||||||
|
|
||||||
const revisions = [];
|
const revisions = [];
|
||||||
|
|
||||||
let prev = NONEXISTENT;
|
let prev: V | symbol = NONEXISTENT;
|
||||||
for (let i = 0; i <= maxRev; ++i) {
|
for (let i = 0; i <= maxRev; ++i) {
|
||||||
let v = prev;
|
let v = prev;
|
||||||
if (del & (1 << i)) v = NONEXISTENT;
|
if (del & (1 << i)) v = NONEXISTENT;
|
||||||
@@ -147,28 +156,28 @@ export default class VersionedMap {
|
|||||||
this.map.set(key, revisions);
|
this.map.set(key, revisions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDiff(key) {
|
public getDiff(key: K): [V, V] {
|
||||||
const revisions = this.map.get(key);
|
const revisions = this.map.get(key);
|
||||||
if (!revisions) return [UNDEFINED, UNDEFINED];
|
if (!revisions) return [UNDEFINED, UNDEFINED];
|
||||||
let first = revisions[0];
|
let first = revisions[0];
|
||||||
if (first === NONEXISTENT) first = UNDEFINED;
|
if (first === NONEXISTENT) first = UNDEFINED;
|
||||||
let last = revisions[revisions.length - 1];
|
let last = revisions[revisions.length - 1];
|
||||||
if (last === NONEXISTENT) last = UNDEFINED;
|
if (last === NONEXISTENT) last = UNDEFINED;
|
||||||
return [first, last];
|
return [first as V, last as V];
|
||||||
}
|
}
|
||||||
|
|
||||||
*diff() {
|
public *diff(): IterableIterator<[K, V, V]> {
|
||||||
for (const [key, revisions] of this.map) {
|
for (const [key, revisions] of this.map) {
|
||||||
let first = revisions[0];
|
let first = revisions[0];
|
||||||
let last = revisions[revisions.length - 1];
|
let last = revisions[revisions.length - 1];
|
||||||
if ((first === last) === NONEXISTENT) continue;
|
if (first === NONEXISTENT && last === NONEXISTENT) continue;
|
||||||
if (first === NONEXISTENT) first = UNDEFINED;
|
if (first === NONEXISTENT) first = UNDEFINED;
|
||||||
if (last === NONEXISTENT) last = UNDEFINED;
|
if (last === NONEXISTENT) last = UNDEFINED;
|
||||||
yield [key, first, last];
|
yield [key, first as V, last as V];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collapse(revision) {
|
public collapse(revision: number): void {
|
||||||
if (this._sizeDiff.length <= revision) return;
|
if (this._sizeDiff.length <= revision) return;
|
||||||
|
|
||||||
this._sizeDiff[revision] = this._sizeDiff[this._sizeDiff.length - 1];
|
this._sizeDiff[revision] = this._sizeDiff[this._sizeDiff.length - 1];
|
||||||
Generated
+9507
-271
File diff suppressed because it is too large.
Load diff
+39
-20
@@ -16,39 +16,54 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"directories": {
|
"directories": {
|
||||||
"bin": "bin"
|
"bin": "bin",
|
||||||
|
"tools": "tools"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonwebtoken": "^8.3.0",
|
"jsonwebtoken": "^8.4.0",
|
||||||
|
"koa": "^2.7.0",
|
||||||
"koa-bodyparser": "^4.2.1",
|
"koa-bodyparser": "^4.2.1",
|
||||||
"koa-compress": "^3.0.0",
|
"koa-compress": "^3.0.0",
|
||||||
"koa-jwt": "^3.3.2",
|
"koa-jwt": "^3.5.1",
|
||||||
"koa-router": "^7.4.0",
|
"koa-router": "^7.4.0",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"koa": "^2.5.2",
|
|
||||||
"later": "^1.2.0",
|
"later": "^1.2.0",
|
||||||
"libxmljs": "^0.19.5",
|
"libxmljs": "^0.19.5",
|
||||||
"mongodb": "^3.1.10",
|
"mongodb": "^3.1.13",
|
||||||
"parsimmon": "^1.12.0",
|
"parsimmon": "^1.12.0",
|
||||||
"seedrandom": "^2.4.4"
|
"seedrandom": "^2.4.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ava": "^1.1.0",
|
"@types/jsonwebtoken": "^8.3.0",
|
||||||
"babel-eslint": "^8.2.6",
|
"@types/koa": "^2.0.48",
|
||||||
"codemirror": "^5.40.0",
|
"@types/koa-compress": "^2.0.8",
|
||||||
"cssnano": "^4.0.5",
|
"@types/koa-router": "^7.0.39",
|
||||||
"eslint": "^5.10.0",
|
"@types/koa-static": "^4.0.1",
|
||||||
"esm": "^3.1.1",
|
"@types/libxmljs": "^0.18.3",
|
||||||
|
"@types/mithril": "^1.1.16",
|
||||||
|
"@types/mongodb": "^3.1.19",
|
||||||
|
"@types/parsimmon": "^1.10.0",
|
||||||
|
"@types/seedrandom": "^2.4.27",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^1.3.0",
|
||||||
|
"ava": "^1.2.1",
|
||||||
|
"codemirror": "^5.43.0",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
|
"eslint": "^5.14.1",
|
||||||
|
"eslint-config-prettier": "^4.0.0",
|
||||||
|
"esm": "^3.2.5",
|
||||||
"mithril": "^1.1.6",
|
"mithril": "^1.1.6",
|
||||||
"postcss-cssnext": "^3.1.0",
|
"postcss-cssnext": "^3.1.0",
|
||||||
"postcss-import": "^11.1.0",
|
"postcss-import": "^12.0.1",
|
||||||
"prettier": "^1.15.3",
|
"prettier": "^1.16.4",
|
||||||
"rollup": "^1.1.2",
|
"rollup": "^1.2.1",
|
||||||
"rollup-plugin-json": "^3.1.0",
|
"rollup-plugin-json": "^3.1.0",
|
||||||
"rollup-plugin-replace": "^2.1.0",
|
"rollup-plugin-replace": "^2.1.0",
|
||||||
"rollup-plugin-terser": "^4.0.2",
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
"webpack": "^4.16.3",
|
"rollup-plugin-typescript": "^1.0.0",
|
||||||
"yaml": "^1.0.0"
|
"ts-node": "^8.0.2",
|
||||||
|
"typescript": "^3.3.3",
|
||||||
|
"webpack": "^4.29.5",
|
||||||
|
"yaml": "^1.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.14.0"
|
"node": ">=8.14.0"
|
||||||
@@ -57,8 +72,12 @@
|
|||||||
"compile": "echo 'Compile step no longer required, skipping.'",
|
"compile": "echo 'Compile step no longer required, skipping.'",
|
||||||
"configure": "if [ ! -f ./config/config.json ]; then cp ./config/config-sample.json ./config/config.json; fi",
|
"configure": "if [ ! -f ./config/config.json ]; then cp ./config/config-sample.json ./config/config.json; fi",
|
||||||
"install": "npm run configure",
|
"install": "npm run configure",
|
||||||
"test": "ava -r esm --verbose --color",
|
"test": "ava --verbose --color",
|
||||||
"lint": "eslint --fix lib ui build test bin/* tools/*; prettier --parser babylon --write \"lib/**/*.js\" \"ui/**/*.js\" \"build/**/*.js\" \"test/**/*.js\" bin/* tools/*",
|
"lint-ln": "find bin -type f -exec sh -c 'ln -fsr \"$1\" \"bin/.${1#bin/}.ts\"' _ {} \\;; find tools -type f -exec sh -c 'ln -fsr \"$1\" \"tools/.${1#tools/}.ts\"' _ {} \\;",
|
||||||
"build": "node build/build"
|
"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-tsc": "tsc --noEmit",
|
||||||
|
"lint": "npm run lint-ln && npm run lint-eslint && npm run lint-prettier && npm run lint-tsc",
|
||||||
|
"build": "node -r esm -r ts-node/register/transpile-only build/build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import ava from "ava";
|
import ava from "ava";
|
||||||
import * as common from "../lib/common.js";
|
import * as common from "../lib/common";
|
||||||
|
import { ParamPath } from "../lib/types";
|
||||||
|
|
||||||
ava("typeOf", t => {
|
ava("typeOf", t => {
|
||||||
const cases = [
|
const cases = [
|
||||||
@@ -65,24 +64,24 @@ ava("generateDeviceId", t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ava("parsePath", t => {
|
ava("parsePath", t => {
|
||||||
const case1 = ["Seg1.Seg2.Seg3", ["Seg1", "Seg2", "Seg3"]];
|
const case1 = ["Seg1.Seg2.Seg3", ["Seg1", "Seg2", "Seg3"]] as ParamPath;
|
||||||
case1[1].alias = 0;
|
case1[1].alias = 0;
|
||||||
case1[1].wildcard = 0;
|
case1[1].wildcard = 0;
|
||||||
|
|
||||||
const case2 = ["Seg1.*.*.Seg4", ["Seg1", "*", "*", "Seg4"]];
|
const case2 = ["Seg1.*.*.Seg4", ["Seg1", "*", "*", "Seg4"]] as ParamPath;
|
||||||
case2[1].alias = 0;
|
case2[1].alias = 0;
|
||||||
case2[1].wildcard = 6;
|
case2[1].wildcard = 6;
|
||||||
|
|
||||||
const case3 = [
|
const case3 = [
|
||||||
"Seg1.Seg2.[Seg6:Seg9].Seg3",
|
"Seg1.Seg2.[Seg6:Seg9].Seg3",
|
||||||
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
|
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
|
||||||
];
|
] as ParamPath;
|
||||||
case3[1].alias = 4;
|
case3[1].alias = 4;
|
||||||
case3[1].wildcard = 0;
|
case3[1].wildcard = 0;
|
||||||
case3[1][2][0].alias = 0;
|
case3[1][2][0].alias = 0;
|
||||||
case3[1][2][0].wildcard = 0;
|
case3[1][2][0].wildcard = 0;
|
||||||
|
|
||||||
const case4 = ["Seg1.[*:SegAny]", ["Seg1", [["*"], "SegAny"]]];
|
const case4 = ["Seg1.[*:SegAny]", ["Seg1", [["*"], "SegAny"]]] as ParamPath;
|
||||||
case4[1].alias = 2;
|
case4[1].alias = 2;
|
||||||
case4[1].wildcard = 0;
|
case4[1].wildcard = 0;
|
||||||
case4[1][1][0].alias = 0;
|
case4[1][1][0].alias = 0;
|
||||||
@@ -93,24 +92,33 @@ ava("parsePath", t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ava("addPathMeta", t => {
|
ava("addPathMeta", t => {
|
||||||
const case1 = [["Seg1", "Seg2", "Seg3"], ["Seg1", "Seg2", "Seg3"]];
|
const case1 = [
|
||||||
|
["Seg1", "Seg2", "Seg3"],
|
||||||
|
["Seg1", "Seg2", "Seg3"]
|
||||||
|
] as ParamPath;
|
||||||
case1[1].alias = 0;
|
case1[1].alias = 0;
|
||||||
case1[1].wildcard = 0;
|
case1[1].wildcard = 0;
|
||||||
|
|
||||||
const case2 = [["Seg1", "*", "*", "Seg4"], ["Seg1", "*", "*", "Seg4"]];
|
const case2 = [
|
||||||
|
["Seg1", "*", "*", "Seg4"],
|
||||||
|
["Seg1", "*", "*", "Seg4"]
|
||||||
|
] as ParamPath;
|
||||||
case2[1].alias = 0;
|
case2[1].alias = 0;
|
||||||
case2[1].wildcard = 6;
|
case2[1].wildcard = 6;
|
||||||
|
|
||||||
const case3 = [
|
const case3 = [
|
||||||
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"],
|
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"],
|
||||||
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
|
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
|
||||||
];
|
] as ParamPath;
|
||||||
case3[1].alias = 4;
|
case3[1].alias = 4;
|
||||||
case3[1].wildcard = 0;
|
case3[1].wildcard = 0;
|
||||||
case3[1][2][0].alias = 0;
|
case3[1][2][0].alias = 0;
|
||||||
case3[1][2][0].wildcard = 0;
|
case3[1][2][0].wildcard = 0;
|
||||||
|
|
||||||
const case4 = [["Seg1", [["*"], "SegAny"]], ["Seg1", [["*"], "SegAny"]]];
|
const case4 = [
|
||||||
|
["Seg1", [["*"], "SegAny"]],
|
||||||
|
["Seg1", [["*"], "SegAny"]]
|
||||||
|
] as ParamPath;
|
||||||
case4[1].alias = 2;
|
case4[1].alias = 2;
|
||||||
case4[1].wildcard = 0;
|
case4[1].wildcard = 0;
|
||||||
case4[1][1][0].alias = 0;
|
case4[1][1][0].alias = 0;
|
||||||
+5
-8
@@ -1,6 +1,4 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
import { get } from "../lib/config";
|
import { get } from "../lib/config";
|
||||||
|
|
||||||
@@ -160,11 +158,10 @@ const configData = {
|
|||||||
"ui.pageSize": "10"
|
"ui.pageSize": "10"
|
||||||
};
|
};
|
||||||
|
|
||||||
async function run() {
|
async function run(): Promise<void> {
|
||||||
const client = await MongoClient.connect(
|
const client = await MongoClient.connect(get("MONGODB_CONNECTION_URL"), {
|
||||||
get("MONGODB_CONNECTION_URL"),
|
useNewUrlParser: true
|
||||||
{ useNewUrlParser: true }
|
});
|
||||||
);
|
|
||||||
const collection = client.db().collection("config");
|
const collection = client.db().collection("config");
|
||||||
await collection.deleteMany({});
|
await collection.deleteMany({});
|
||||||
const objs = Object.entries(configData).map(c => ({
|
const objs = Object.entries(configData).map(c => ({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env -S node -r esm
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2017 Zaid Abdulla
|
* Copyright 2013-2017 Zaid Abdulla
|
||||||
*
|
*
|
||||||
@@ -17,10 +17,9 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import fs from "fs";
|
import * as fs from "fs";
|
||||||
import path from "path";
|
import * as path from "path";
|
||||||
import { connect, devicesCollection } from "../lib/db";
|
import { connect, devicesCollection } from "../lib/db";
|
||||||
|
|
||||||
const outputFilename = path.resolve(`data_model_${process.argv[2]}.json`);
|
const outputFilename = path.resolve(`data_model_${process.argv[2]}.json`);
|
||||||
@@ -28,7 +27,7 @@ process.chdir(path.resolve(path.dirname(fs.realpathSync(__filename)), ".."));
|
|||||||
|
|
||||||
const dataModel = {};
|
const dataModel = {};
|
||||||
|
|
||||||
function recursive(object, p) {
|
function recursive(object, p): void {
|
||||||
if (!p) p = "";
|
if (!p) p = "";
|
||||||
|
|
||||||
for (const key in object) {
|
for (const key in object) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"esnext", "dom"
|
||||||
|
],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "esnext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./bin/.*.ts",
|
||||||
|
"./lib/**/*",
|
||||||
|
"./ui/**/*",
|
||||||
|
"./test/**/*",
|
||||||
|
"./build/**/*",
|
||||||
|
"./toos/.*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,9 +2,5 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"node": false
|
"node": false
|
||||||
},
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"parserOptions": {
|
|
||||||
"allowImportExportEverywhere": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import m from "mithril";
|
import m from "mithril";
|
||||||
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
export default function menu() {
|
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 = [];
|
||||||
|
|
||||||
if (window.authorizer.hasAccess("presets", 1)) {
|
if (window.authorizer.hasAccess("presets", 1)) {
|
||||||
@@ -61,4 +60,6 @@ export default function menu() {
|
|||||||
return m("nav#side-menu", m("ul", tabs));
|
return m("nav#side-menu", m("ul", tabs));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
+20
-11
@@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
import m, { RouteResolver } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
|
||||||
|
|
||||||
import layout from "./layout";
|
import layout from "./layout";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
import * as loginPage from "./login-page";
|
import * as loginPage from "./login-page";
|
||||||
@@ -19,6 +16,15 @@ import Authorizer from "../lib//common/authorizer";
|
|||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
import { contextifyComponent } from "./components";
|
import { contextifyComponent } from "./components";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
authorizer: Authorizer;
|
||||||
|
permissionSets: any;
|
||||||
|
username: string;
|
||||||
|
clientConfig: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.authorizer = new Authorizer(window.permissionSets);
|
window.authorizer = new Authorizer(window.permissionSets);
|
||||||
|
|
||||||
const adminPages = [
|
const adminPages = [
|
||||||
@@ -35,7 +41,7 @@ let lastRenderTimestamp = 0;
|
|||||||
let pageVisitTimestamp = 0;
|
let pageVisitTimestamp = 0;
|
||||||
let fulfillTimeout = null;
|
let fulfillTimeout = null;
|
||||||
|
|
||||||
function fulfill() {
|
function fulfill(): void {
|
||||||
clearTimeout(fulfillTimeout);
|
clearTimeout(fulfillTimeout);
|
||||||
fulfillTimeout = setTimeout(() => {
|
fulfillTimeout = setTimeout(() => {
|
||||||
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => {
|
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => {
|
||||||
@@ -44,16 +50,19 @@ function fulfill() {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pagify(pageName, page) {
|
function pagify(pageName, page): RouteResolver {
|
||||||
const component = {
|
const component: RouteResolver = {
|
||||||
render: () => {
|
render: () => {
|
||||||
lastRenderTimestamp = Date.now();
|
lastRenderTimestamp = Date.now();
|
||||||
let p;
|
let p;
|
||||||
if (state && state.error) p = m(errorPage.component, state);
|
if (state && state.error) p = m(errorPage.component, state);
|
||||||
else p = m(contextifyComponent(page.component), state);
|
else p = m(contextifyComponent(page.component), state);
|
||||||
fulfill();
|
fulfill();
|
||||||
return m(layout, { page: pageName }, p);
|
const attrs = {};
|
||||||
}
|
attrs["page"] = pageName;
|
||||||
|
return m(layout, attrs, p);
|
||||||
|
},
|
||||||
|
onmatch: null
|
||||||
};
|
};
|
||||||
|
|
||||||
component.onmatch = (args, requestedPath) => {
|
component.onmatch = (args, requestedPath) => {
|
||||||
@@ -87,8 +96,8 @@ function pagify(pageName, page) {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectAdminPage() {
|
function redirectAdminPage(): RouteResolver {
|
||||||
const component = {
|
const component: RouteResolver = {
|
||||||
onmatch: () => {
|
onmatch: () => {
|
||||||
for (const page of adminPages) {
|
for (const page of adminPages) {
|
||||||
if (window.authorizer.hasAccess(page, 2)) {
|
if (window.authorizer.hasAccess(page, 2)) {
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
"use strict";
|
export default class Autocomplete {
|
||||||
|
private callback: (
|
||||||
|
value: string,
|
||||||
|
callback: (suggestions: string[]) => void
|
||||||
|
) => void;
|
||||||
|
private element: HTMLInputElement;
|
||||||
|
private hideTimeout: NodeJS.Timeout;
|
||||||
|
private visible: boolean;
|
||||||
|
private default: string;
|
||||||
|
private selection: number;
|
||||||
|
private container: HTMLElement;
|
||||||
|
|
||||||
class Autocomplete {
|
public constructor(className, callback) {
|
||||||
constructor(className, callback) {
|
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.element = null;
|
this.element = null;
|
||||||
this.hideTimeout = null;
|
this.hideTimeout = null;
|
||||||
@@ -12,11 +21,11 @@ class Autocomplete {
|
|||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.position = "absolute";
|
this.container.style.position = "absolute";
|
||||||
this.container.style.display = "block";
|
this.container.style.display = "block";
|
||||||
this.container.style.opacity = 0;
|
this.container.style.opacity = "0";
|
||||||
this.container.className = className;
|
this.container.className = className;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(el) {
|
public attach(el): void {
|
||||||
el.setAttribute("autocomplete", "off");
|
el.setAttribute("autocomplete", "off");
|
||||||
|
|
||||||
el.addEventListener("focus", () => {
|
el.addEventListener("focus", () => {
|
||||||
@@ -63,8 +72,8 @@ class Autocomplete {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
private hide(): void {
|
||||||
this.container.style.opacity = 0;
|
this.container.style.opacity = "0";
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.default = null;
|
this.default = null;
|
||||||
this.selection = null;
|
this.selection = null;
|
||||||
@@ -77,7 +86,7 @@ class Autocomplete {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
private update(): void {
|
||||||
const el = this.element;
|
const el = this.element;
|
||||||
|
|
||||||
this.callback(el.value, suggestions => {
|
this.callback(el.value, suggestions => {
|
||||||
@@ -101,7 +110,7 @@ class Autocomplete {
|
|||||||
clearTimeout(this.hideTimeout);
|
clearTimeout(this.hideTimeout);
|
||||||
this.hideTimeout = null;
|
this.hideTimeout = null;
|
||||||
}
|
}
|
||||||
this.container.style.opacity = 1;
|
this.container.style.opacity = "1";
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +137,7 @@ class Autocomplete {
|
|||||||
e.addEventListener("mousedown", ev => {
|
e.addEventListener("mousedown", ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
el.value = suggestion;
|
el.value = suggestion;
|
||||||
if (this.element === el) this.update(el);
|
if (this.element === el) this.update();
|
||||||
});
|
});
|
||||||
this.container.appendChild(e);
|
this.container.appendChild(e);
|
||||||
}
|
}
|
||||||
@@ -150,5 +159,3 @@ class Autocomplete {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Autocomplete;
|
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
"use strict";
|
import m, {
|
||||||
|
Static,
|
||||||
import m from "mithril";
|
Attributes,
|
||||||
|
Children,
|
||||||
|
ComponentTypes,
|
||||||
|
Lifecycle,
|
||||||
|
ClosureComponent,
|
||||||
|
Vnode
|
||||||
|
} from "mithril";
|
||||||
import parameter from "./components/parameter";
|
import parameter from "./components/parameter";
|
||||||
import parameterList from "./components/parameter-list";
|
import parameterList from "./components/parameter-list";
|
||||||
import parameterTable from "./components/parameter-table";
|
import parameterTable from "./components/parameter-table";
|
||||||
@@ -31,13 +37,33 @@ const comps = {
|
|||||||
"long-text": longTextComponent
|
"long-text": longTextComponent
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextifiedComponents = new WeakMap();
|
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
||||||
const vnodeContext = new WeakMap();
|
const vnodeContext = new WeakMap<Vnode, Attributes>();
|
||||||
|
|
||||||
function contextFn(context, ...argumentsList) {
|
interface MC extends Static {
|
||||||
const vnode = Reflect.apply(M, undefined, argumentsList);
|
context: {
|
||||||
vnodeContext.set(vnode, context);
|
(ctx: Attributes, selector: string, ...children: Children[]): Vnode<
|
||||||
return vnode;
|
any,
|
||||||
|
any
|
||||||
|
>;
|
||||||
|
(
|
||||||
|
ctx: Attributes,
|
||||||
|
selector: string,
|
||||||
|
attributes: Attributes,
|
||||||
|
...children: Children[]
|
||||||
|
): Vnode<any, any>;
|
||||||
|
<Attrs, State>(
|
||||||
|
ctx: Attributes,
|
||||||
|
component: ComponentTypes<Attrs, State>,
|
||||||
|
...args: Children[]
|
||||||
|
): Vnode<Attrs, State>;
|
||||||
|
<Attrs, State>(
|
||||||
|
ctx: Attributes,
|
||||||
|
component: ComponentTypes<Attrs, State>,
|
||||||
|
attributes: Attrs & Lifecycle<Attrs, State> & { key?: string | number },
|
||||||
|
...args: Children[]
|
||||||
|
): Vnode<Attrs, State>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const M = new Proxy(m, {
|
const M = new Proxy(m, {
|
||||||
@@ -52,9 +78,15 @@ const M = new Proxy(m, {
|
|||||||
if (prop === "context") return contextFn;
|
if (prop === "context") return contextFn;
|
||||||
else return Reflect.get(target, prop);
|
else return Reflect.get(target, prop);
|
||||||
}
|
}
|
||||||
});
|
}) as MC;
|
||||||
|
|
||||||
function applyContext(vnode, parentContext) {
|
function contextFn(context, ...argumentsList): Vnode {
|
||||||
|
const vnode = Reflect.apply(M, undefined, argumentsList);
|
||||||
|
vnodeContext.set(vnode, context);
|
||||||
|
return vnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyContext(vnode, parentContext): void {
|
||||||
if (Array.isArray(vnode)) {
|
if (Array.isArray(vnode)) {
|
||||||
for (const c of vnode) applyContext(c, parentContext);
|
for (const c of vnode) applyContext(c, parentContext);
|
||||||
} else if (vnode && typeof vnode === "object" && vnode.tag) {
|
} else if (vnode && typeof vnode === "object" && vnode.tag) {
|
||||||
@@ -66,25 +98,25 @@ function applyContext(vnode, parentContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function contextifyComponent(component) {
|
export function contextifyComponent(component: ComponentTypes): ComponentTypes {
|
||||||
let c = contextifiedComponents.get(component);
|
let c = contextifiedComponents.get(component);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
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 = vnode => {
|
c.view = function(vnode) {
|
||||||
const context = vnodeContext.get(vnode) || {};
|
const context = vnodeContext.get(vnode) || {};
|
||||||
const res = view(vnode, M);
|
const res = Reflect.apply(view, this, [vnode]);
|
||||||
applyContext(res, context);
|
applyContext(res, context);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
} else if (!component.prototype.view) {
|
} else if (!component.prototype || !component.prototype.view) {
|
||||||
c = initialNode => {
|
c = initialNode => {
|
||||||
const state = component(initialNode);
|
const state = (component as ClosureComponent)(initialNode);
|
||||||
const view = state.view;
|
const view = state.view;
|
||||||
state.view = vnode => {
|
state.view = function(vnode) {
|
||||||
const context = vnodeContext.get(vnode) || {};
|
const context = vnodeContext.get(vnode) || {};
|
||||||
const res = view(vnode, M);
|
const res = Reflect.apply(view, this, [vnode]);
|
||||||
applyContext(res, context);
|
applyContext(res, context);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import { parse } from "../../lib/common/expression-parser";
|
import { parse } from "../../lib/common/expression-parser";
|
||||||
@@ -7,30 +6,30 @@ import memoize from "../../lib/common/memoize";
|
|||||||
|
|
||||||
const memoizedParse = memoize(parse);
|
const memoizedParse = memoize(parse);
|
||||||
|
|
||||||
function escapeRegExp(str) {
|
function escapeRegExp(str): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function 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");
|
||||||
}
|
}
|
||||||
@@ -43,7 +42,7 @@ export default function component() {
|
|||||||
const attrs = {};
|
const attrs = {};
|
||||||
if (re) {
|
if (re) {
|
||||||
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
||||||
if (!re.test(str)) attrs.style = "display: none;";
|
if (!re.test(str)) attrs["style"] = "display: none;";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.object === false) {
|
if (p.object === false) {
|
||||||
@@ -132,4 +131,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { m } from "../components";
|
|
||||||
import * as store from "../store";
|
|
||||||
|
|
||||||
export default function component() {
|
|
||||||
return {
|
|
||||||
view: vnode => {
|
|
||||||
if (vnode.attrs.filter) {
|
|
||||||
if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const children = Object.values(vnode.attrs.components).map(c => {
|
|
||||||
if (typeof c !== "object") return `${c}`;
|
|
||||||
return m(c.type, c);
|
|
||||||
});
|
|
||||||
if (vnode.attrs.element) return m(vnode.attrs.element, children);
|
|
||||||
else return children;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
import { m } from "../components";
|
||||||
|
import * as store from "../store";
|
||||||
|
|
||||||
|
const component: ClosureComponent = (): Component => {
|
||||||
|
return {
|
||||||
|
view: vnode => {
|
||||||
|
if (vnode.attrs["filter"]) {
|
||||||
|
if (
|
||||||
|
!store.evaluateExpression(
|
||||||
|
vnode.attrs["filter"],
|
||||||
|
vnode.attrs["device"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = Object.values(vnode.attrs["components"]).map(c => {
|
||||||
|
if (typeof c !== "object") return `${c}`;
|
||||||
|
return m(c["type"], c);
|
||||||
|
});
|
||||||
|
if (vnode.attrs["element"]) return m(vnode.attrs["element"], children);
|
||||||
|
else return children;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import * as notifications from "../notifications";
|
import * as notifications from "../notifications";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
|
|
||||||
export default function 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 = [];
|
||||||
|
|
||||||
@@ -87,4 +86,6 @@ export default function component() {
|
|||||||
return m(".actions-bar", buttons);
|
return m(".actions-bar", buttons);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
import * as notifications from "../notifications";
|
import * as notifications from "../notifications";
|
||||||
|
|
||||||
export default function 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",
|
||||||
@@ -72,4 +71,6 @@ export default function component() {
|
|||||||
return m("table.table", thead, tbody);
|
return m("table.table", thead, tbody);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
|
|
||||||
export default function 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);
|
||||||
});
|
});
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
return m(
|
return m(
|
||||||
@@ -25,4 +24,6 @@ export default function component() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
|
|
||||||
const CHARTS = config.ui.overview.charts;
|
const CHARTS = config.ui.overview.charts;
|
||||||
|
|
||||||
export default function 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"]];
|
||||||
for (const slice of Object.values(chart.slices)) {
|
for (const slice of Object.values(chart.slices)) {
|
||||||
const filter = slice.filter;
|
const filter = slice["filter"];
|
||||||
if (store.evaluateExpression(filter, device)) {
|
if (store.evaluateExpression(filter, device)) {
|
||||||
const dot = m(
|
const dot = m(
|
||||||
"svg",
|
"svg",
|
||||||
@@ -26,13 +25,15 @@ export default function component() {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { m } from "../components";
|
|
||||||
|
|
||||||
export default function component() {
|
|
||||||
return {
|
|
||||||
view: vnode => {
|
|
||||||
const device = vnode.attrs.device;
|
|
||||||
|
|
||||||
const rows = Object.values(vnode.attrs.parameters).map(parameter => {
|
|
||||||
const p = m.context(
|
|
||||||
{
|
|
||||||
device: device,
|
|
||||||
parameter: parameter.parameter
|
|
||||||
},
|
|
||||||
parameter.type || "parameter",
|
|
||||||
parameter
|
|
||||||
);
|
|
||||||
|
|
||||||
return m(
|
|
||||||
"tr",
|
|
||||||
{
|
|
||||||
onupdate: vn => {
|
|
||||||
vn.dom.style.display = p.dom ? "" : "none";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
m("th", parameter.label),
|
|
||||||
m("td", p)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return m("table.parameter-list", rows);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||||
|
import { m } from "../components";
|
||||||
|
|
||||||
|
const component: ClosureComponent = (): Component => {
|
||||||
|
return {
|
||||||
|
view: vnode => {
|
||||||
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
|
const rows = Object.values(vnode.attrs["parameters"]).map(parameter => {
|
||||||
|
const p = m.context(
|
||||||
|
{
|
||||||
|
device: device,
|
||||||
|
parameter: parameter["parameter"]
|
||||||
|
},
|
||||||
|
parameter["type"] || "parameter",
|
||||||
|
parameter
|
||||||
|
);
|
||||||
|
|
||||||
|
return m(
|
||||||
|
"tr",
|
||||||
|
{
|
||||||
|
onupdate: vn => {
|
||||||
|
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||||
|
? ""
|
||||||
|
: "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
m("th", parameter["label"]),
|
||||||
|
m("td", p)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return m("table.parameter-list", rows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
import * as expressionParser from "../../lib/common/expression-parser";
|
import * as expressionParser from "../../lib/common/expression-parser";
|
||||||
|
|
||||||
export default function 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 (!device[object]) return null;
|
||||||
|
|
||||||
@@ -31,13 +30,14 @@ export default function component() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = Object.values(parameters).map(p => m("th", p.label));
|
const headers = Object.values(parameters).map(p => m("th", p["label"]));
|
||||||
|
|
||||||
const thead = m("thead", m("tr", headers));
|
const thead = m("thead", m("tr", headers));
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (const i of instances) {
|
for (const i of instances) {
|
||||||
let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
|
let filter =
|
||||||
|
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
||||||
|
|
||||||
filter = expressionParser.map(filter, e => {
|
filter = expressionParser.map(filter, e => {
|
||||||
if (Array.isArray(e) && e[0] === "PARAM")
|
if (Array.isArray(e) && e[0] === "PARAM")
|
||||||
@@ -126,9 +126,11 @@ export default function component() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let label;
|
let label;
|
||||||
if (vnode.attrs.label) label = m("h2", vnode.attrs.label);
|
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
||||||
|
|
||||||
return [label, m("table.table", thead, m("tbody", rows))];
|
return [label, m("table.table", thead, m("tbody", rows))];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
@@ -31,14 +30,14 @@ const evaluateParam = memoize((exp, obj, now) => {
|
|||||||
return { value, timestamp, parameter };
|
return { value, timestamp, parameter };
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function 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(
|
||||||
vnode.attrs.parameter,
|
vnode.attrs["parameter"],
|
||||||
vnode.attrs.device,
|
vnode.attrs["device"],
|
||||||
store.getTimestamp()
|
store.getTimestamp()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ export default function component() {
|
|||||||
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.dom) {
|
if (e.target === (el as VnodeDOM).dom) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
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)})`;
|
||||||
@@ -89,4 +88,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { m } from "../components";
|
|
||||||
import * as store from "../store";
|
|
||||||
|
|
||||||
const REFRESH_INTERVAL = 3000;
|
|
||||||
|
|
||||||
export default function component() {
|
|
||||||
return {
|
|
||||||
oninit: vnode => {
|
|
||||||
vnode.state.timestamp = 0;
|
|
||||||
vnode.state.ping = null;
|
|
||||||
vnode.state.host = null;
|
|
||||||
vnode.state.timeout = null;
|
|
||||||
},
|
|
||||||
view: vnode => {
|
|
||||||
const refresh = () => {
|
|
||||||
const device = vnode.attrs.device;
|
|
||||||
let param =
|
|
||||||
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
|
||||||
if (!param)
|
|
||||||
param = device["Device.ManagementServer.ConnectionRequestURL"];
|
|
||||||
if (!param || !param.value) return;
|
|
||||||
|
|
||||||
const url = new URL(param.value[0]);
|
|
||||||
vnode.state.host = url.hostname;
|
|
||||||
vnode.state.timestamp = Date.now();
|
|
||||||
store
|
|
||||||
.ping(vnode.state.host)
|
|
||||||
.then(res => {
|
|
||||||
vnode.state.ping = res;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Do nothing
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const t = vnode.state.timestamp + REFRESH_INTERVAL - Date.now();
|
|
||||||
clearTimeout(vnode.state.timeout);
|
|
||||||
if (t <= 0) refresh();
|
|
||||||
else vnode.state.timeout = setTimeout(refresh, t);
|
|
||||||
|
|
||||||
if (vnode.state.host) {
|
|
||||||
let status = "";
|
|
||||||
if (vnode.state.ping) {
|
|
||||||
if (vnode.state.ping.avg != null)
|
|
||||||
status = `${Math.trunc(vnode.state.ping.avg)} ms`;
|
|
||||||
else status = "Unreachable";
|
|
||||||
}
|
|
||||||
|
|
||||||
return m("div", `Pinging ${vnode.state.host}: ${status}`);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
import { m } from "../components";
|
||||||
|
import * as store from "../store";
|
||||||
|
|
||||||
|
const REFRESH_INTERVAL = 3000;
|
||||||
|
|
||||||
|
const component: ClosureComponent = (): Component => {
|
||||||
|
return {
|
||||||
|
oninit: vnode => {
|
||||||
|
vnode.state["timestamp"] = 0;
|
||||||
|
vnode.state["ping"] = null;
|
||||||
|
vnode.state["host"] = null;
|
||||||
|
vnode.state["timeout"] = null;
|
||||||
|
},
|
||||||
|
view: vnode => {
|
||||||
|
const refresh = (): void => {
|
||||||
|
const device = vnode.attrs["device"];
|
||||||
|
let param =
|
||||||
|
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
||||||
|
if (!param)
|
||||||
|
param = device["Device.ManagementServer.ConnectionRequestURL"];
|
||||||
|
if (!param || !param.value) return;
|
||||||
|
|
||||||
|
const url = new URL(param.value[0]);
|
||||||
|
vnode.state["host"] = url.hostname;
|
||||||
|
vnode.state["timestamp"] = Date.now();
|
||||||
|
store
|
||||||
|
.ping(vnode.state["host"])
|
||||||
|
.then(res => {
|
||||||
|
vnode.state["ping"] = res;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = vnode.state["timestamp"] + REFRESH_INTERVAL - Date.now();
|
||||||
|
clearTimeout(vnode.state["timeout"]);
|
||||||
|
if (t <= 0) refresh();
|
||||||
|
else vnode.state["timeout"] = setTimeout(refresh, t);
|
||||||
|
|
||||||
|
if (vnode.state["host"]) {
|
||||||
|
let status = "";
|
||||||
|
if (vnode.state["ping"]) {
|
||||||
|
if (vnode.state["ping"].avg != null)
|
||||||
|
status = `${Math.trunc(vnode.state["ping"].avg)} ms`;
|
||||||
|
else status = "Unreachable";
|
||||||
|
}
|
||||||
|
|
||||||
|
return m("div", `Pinging ${vnode.state["host"]}: ${status}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
import * as notifications from "../notifications";
|
import * as notifications from "../notifications";
|
||||||
|
|
||||||
export default function 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",
|
||||||
@@ -16,9 +15,9 @@ export default function component() {
|
|||||||
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);
|
||||||
})
|
})
|
||||||
.filter(exp => !!exp);
|
.filter(exp => !!exp);
|
||||||
@@ -68,4 +67,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as notifications from "../notifications";
|
import * as notifications from "../notifications";
|
||||||
import * as store from "../store";
|
import * as store from "../store";
|
||||||
|
|
||||||
export default function 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"];
|
||||||
|
|
||||||
const tags = [];
|
const tags = [];
|
||||||
for (const p of Object.keys(device))
|
for (const p of Object.keys(device))
|
||||||
@@ -87,4 +86,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
@@ -7,13 +5,18 @@ import putFormComponent from "./put-form-component";
|
|||||||
import * as overlay from "./overlay";
|
import * as overlay from "./overlay";
|
||||||
import * as expressionParser from "../lib/common/expression-parser";
|
import * as expressionParser from "../lib/common/expression-parser";
|
||||||
import { loadCodeMirror } from "./dynamic-loader";
|
import { loadCodeMirror } from "./dynamic-loader";
|
||||||
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
const attributes = [
|
const attributes = [
|
||||||
{ id: "_id", label: "Key" },
|
{ id: "_id", label: "Key" },
|
||||||
{ id: "value", label: "Value" }
|
{ id: "value", label: "Value" }
|
||||||
];
|
];
|
||||||
|
|
||||||
function putActionHandler(action, _object, isNew) {
|
interface ValidationErrors {
|
||||||
|
[prop: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const object = Object.assign({}, _object);
|
const object = Object.assign({}, _object);
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
@@ -78,11 +81,11 @@ const formData = {
|
|||||||
attributes: attributes
|
attributes: attributes
|
||||||
};
|
};
|
||||||
|
|
||||||
function escapeRegExp(str) {
|
function escapeRegExp(str): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init(): Promise<{}> {
|
||||||
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")
|
||||||
@@ -97,7 +100,7 @@ export function init() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTable(confsResponse, searchString) {
|
function renderTable(confsResponse, searchString): Children {
|
||||||
const confs = confsResponse.value.sort((a, b) => {
|
const confs = confsResponse.value.sort((a, b) => {
|
||||||
return a._id < b._id ? -1 : 1;
|
return a._id < b._id ? -1 : 1;
|
||||||
});
|
});
|
||||||
@@ -113,14 +116,14 @@ function renderTable(confsResponse, searchString) {
|
|||||||
for (const conf of confs) {
|
for (const conf of confs) {
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
if (regex && !regex.test(conf._id) && !regex.test(conf.value))
|
if (regex && !regex.test(conf._id) && !regex.test(conf.value))
|
||||||
attrs.style = "display: none;";
|
attrs["style"] = "display: none;";
|
||||||
|
|
||||||
const edit = m(
|
const edit = m(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
title: "Edit config value",
|
title: "Edit config value",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -130,9 +133,9 @@ function renderTable(confsResponse, searchString) {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, false)
|
putActionHandler(action, object, false)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const ErrorList = errors ? Object.values(errors) : [];
|
||||||
if (errors.length) {
|
if (ErrorList.length) {
|
||||||
for (const err of errors)
|
for (const err of ErrorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -190,7 +193,7 @@ function renderTable(confsResponse, searchString) {
|
|||||||
return m("table", m("tbody", rows));
|
return m("table", m("tbody", rows));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Config - GenieACS";
|
document.title = "Config - GenieACS";
|
||||||
@@ -199,10 +202,10 @@ export function component() {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,7 +216,7 @@ export function component() {
|
|||||||
{
|
{
|
||||||
title: "Create new config",
|
title: "Create new config",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -222,9 +225,11 @@ export function component() {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, true)
|
putActionHandler(action, object, true)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -259,11 +264,11 @@ export function component() {
|
|||||||
m(
|
m(
|
||||||
".parameter-list",
|
".parameter-list",
|
||||||
{ style: "height: 400px" },
|
{ style: "height: 400px" },
|
||||||
renderTable(confs, vnode.state.searchString)
|
renderTable(confs, vnode.state["searchString"])
|
||||||
),
|
),
|
||||||
m(".actions-bar", newConfig)
|
m(".actions-bar", newConfig)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,3 +1 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
export default window.clientConfig;
|
export default window.clientConfig;
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
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) {
|
export function init(args): Promise<{}> {
|
||||||
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")
|
||||||
@@ -17,12 +16,12 @@ export function init(args) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function 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;
|
||||||
if (!dev.length) return "Loading";
|
if (!dev.length) return "Loading";
|
||||||
const conf = config.ui.device;
|
const conf = config.ui.device;
|
||||||
const cmps = [];
|
const cmps = [];
|
||||||
@@ -30,7 +29,7 @@ export function component() {
|
|||||||
for (const c of Object.values(conf))
|
for (const c of Object.values(conf))
|
||||||
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];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } 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";
|
||||||
@@ -42,7 +41,7 @@ const unpackSmartQuery = memoize(query => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
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"));
|
||||||
@@ -63,7 +62,7 @@ function renderTable(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
sort,
|
sort,
|
||||||
onSortChange
|
onSortChange
|
||||||
) {
|
): Children {
|
||||||
const devices = devicesResponse.value;
|
const devices = devicesResponse.value;
|
||||||
const selectAll = m("input", {
|
const selectAll = m("input", {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
@@ -195,7 +194,7 @@ function renderTable(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderActions(selected) {
|
function renderActions(selected): Children {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
@@ -336,65 +335,68 @@ function renderActions(selected) {
|
|||||||
return m(".actions-bar", buttons);
|
return m(".actions-bar", buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Devices - GenieACS";
|
document.title = "Devices - GenieACS";
|
||||||
|
|
||||||
function showMore() {
|
function showMore(): void {
|
||||||
vnode.state.showCount =
|
vnode.state["showCount"] =
|
||||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChanged(filter) {
|
function onFilterChanged(filter): void {
|
||||||
const ops = { filter };
|
const ops = { filter };
|
||||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||||
m.route.set("/devices", ops);
|
m.route.set("/devices", ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sort) {
|
function onSortChange(sort): void {
|
||||||
const ops = { sort };
|
const ops = { sort };
|
||||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/devices", ops);
|
m.route.set("/devices", ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs["sort"]
|
||||||
let filter = vnode.attrs.filter
|
? memoizedJsonParse(vnode.attrs["sort"])
|
||||||
? memoizedParse(vnode.attrs.filter)
|
: {};
|
||||||
|
let filter = vnode.attrs["filter"]
|
||||||
|
? memoizedParse(vnode.attrs["filter"])
|
||||||
: true;
|
: true;
|
||||||
filter = unpackSmartQuery(filter);
|
filter = unpackSmartQuery(filter);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
const selected = new Set();
|
const selected = new Set();
|
||||||
if (vnode.state.selected) {
|
if (vnode.state["selected"]) {
|
||||||
for (const d of devs.value) {
|
for (const d of devs.value) {
|
||||||
if (vnode.state.selected.has(d["DeviceID.ID"].value[0]))
|
if (vnode.state["selected"].has(d["DeviceID.ID"].value[0]))
|
||||||
selected.add(d["DeviceID.ID"].value[0]);
|
selected.add(d["DeviceID.ID"].value[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vnode.state.selected = selected;
|
vnode.state["selected"] = selected;
|
||||||
|
|
||||||
const downloadUrl = getDownloadUrl(
|
const downloadUrl = getDownloadUrl(
|
||||||
vnode.attrs.filter,
|
vnode.attrs["filter"],
|
||||||
vnode.attrs.indexParameters
|
vnode.attrs["indexParameters"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["resource"] = "devices";
|
||||||
|
attrs["filter"] = vnode.attrs["filter"];
|
||||||
|
attrs["onChange"] = onFilterChanged;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("h1", "Listing devices"),
|
m("h1", "Listing devices"),
|
||||||
m(filterComponent, {
|
m(filterComponent, attrs),
|
||||||
resource: "devices",
|
|
||||||
filter: vnode.attrs.filter,
|
|
||||||
onChange: onFilterChanged
|
|
||||||
}),
|
|
||||||
renderTable(
|
renderTable(
|
||||||
devs,
|
devs,
|
||||||
vnode.attrs.indexParameters,
|
vnode.attrs["indexParameters"],
|
||||||
count.value,
|
count.value,
|
||||||
selected,
|
selected,
|
||||||
showMore,
|
showMore,
|
||||||
@@ -406,4 +408,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
"use strict";
|
import m, {
|
||||||
|
Children,
|
||||||
import m from "mithril";
|
Child,
|
||||||
|
ClosureComponent,
|
||||||
|
Component,
|
||||||
|
VnodeDOM
|
||||||
|
} from "mithril";
|
||||||
import * as taskQueue from "./task-queue";
|
import * as taskQueue from "./task-queue";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
|
|
||||||
function mparam(param) {
|
function mparam(param): Children {
|
||||||
return m("span.parameter", { title: param }, `${param}`);
|
return m("span.parameter", { title: param }, `${param}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mval(val) {
|
function mval(val): Children {
|
||||||
return m("span.value", { title: val }, `${val}`);
|
return m("span.value", { title: val }, `${val}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStagingSpv(task, queueFunc, cancelFunc) {
|
function renderStagingSpv(task, queueFunc, cancelFunc): Children {
|
||||||
function keydown(e) {
|
function keydown(e): void {
|
||||||
if (e.keyCode === 13) queueFunc();
|
if (e.keyCode === 13) queueFunc();
|
||||||
else if (e.keyCode === 27) cancelFunc();
|
else if (e.keyCode === 27) cancelFunc();
|
||||||
else e.redraw = false;
|
else e.redraw = false;
|
||||||
@@ -33,7 +36,7 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
|||||||
},
|
},
|
||||||
onkeydown: keydown,
|
onkeydown: keydown,
|
||||||
oncreate: vnode => {
|
oncreate: vnode => {
|
||||||
vnode.dom.focus();
|
(vnode.dom as HTMLSelectElement).focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -55,11 +58,11 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
|||||||
},
|
},
|
||||||
onkeydown: keydown,
|
onkeydown: keydown,
|
||||||
oncreate: vnode => {
|
oncreate: vnode => {
|
||||||
vnode.dom.focus();
|
(vnode.dom as HTMLInputElement).focus();
|
||||||
vnode.dom.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.scrollTop = 0;
|
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -67,7 +70,7 @@ function renderStagingSpv(task, queueFunc, cancelFunc) {
|
|||||||
return [m("span", "Editing ", mparam(task.parameterValues[0][0])), input];
|
return [m("span", "Editing ", mparam(task.parameterValues[0][0])), input];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStagingDownload(task) {
|
function renderStagingDownload(task): Children {
|
||||||
task.invalid = !task.fileName || !task.fileType;
|
task.invalid = !task.fileName || !task.fileType;
|
||||||
const files = store.fetch("files", true);
|
const files = store.fetch("files", true);
|
||||||
const idParts = task.device.split("-");
|
const idParts = task.device.split("-");
|
||||||
@@ -138,22 +141,22 @@ function renderStagingDownload(task) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStaging(staging) {
|
function renderStaging(staging): Child[] {
|
||||||
const elements = [];
|
const elements: Child[] = [];
|
||||||
|
|
||||||
for (const s of staging) {
|
for (const s of staging) {
|
||||||
const queueFunc = () => {
|
const queueFunc = (): void => {
|
||||||
staging.delete(s);
|
staging.delete(s);
|
||||||
taskQueue.queueTask(s);
|
taskQueue.queueTask(s);
|
||||||
};
|
};
|
||||||
const cancelFunc = () => {
|
const cancelFunc = (): void => {
|
||||||
staging.delete(s);
|
staging.delete(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
let elms;
|
let elms;
|
||||||
if (s.name === "setParameterValues")
|
if (s.name === "setParameterValues")
|
||||||
elms = renderStagingSpv(s, queueFunc, cancelFunc);
|
elms = renderStagingSpv(s, queueFunc, cancelFunc);
|
||||||
else if (s.name === "download")
|
else if (s.name === "download") elms = renderStagingDownload(s);
|
||||||
elms = renderStagingDownload(s, queueFunc, cancelFunc);
|
|
||||||
|
|
||||||
const queue = m(
|
const queue = m(
|
||||||
"button.primary",
|
"button.primary",
|
||||||
@@ -171,9 +174,9 @@ function renderStaging(staging) {
|
|||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderQueue(queue) {
|
function renderQueue(queue): Child[] {
|
||||||
const details = [];
|
const details: Child[] = [];
|
||||||
const devices = {};
|
const devices: { [deviceId: string]: any[] } = {};
|
||||||
for (const t of queue) {
|
for (const t of queue) {
|
||||||
devices[t.device] = devices[t.device] || [];
|
devices[t.device] = devices[t.device] || [];
|
||||||
devices[t.device].push(t);
|
devices[t.device].push(t);
|
||||||
@@ -282,8 +285,8 @@ function renderQueue(queue) {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNotifications(notifs) {
|
function renderNotifications(notifs): Child[] {
|
||||||
const notificationElements = [];
|
const notificationElements: Child[] = [];
|
||||||
|
|
||||||
for (const n of notifs) {
|
for (const n of notifs) {
|
||||||
notificationElements.push(
|
notificationElements.push(
|
||||||
@@ -293,10 +296,10 @@ function renderNotifications(notifs) {
|
|||||||
class: n.type,
|
class: n.type,
|
||||||
style: "position: absolute;opacity: 0",
|
style: "position: absolute;opacity: 0",
|
||||||
oncreate: vnode => {
|
oncreate: vnode => {
|
||||||
vnode.dom.style.opacity = 1;
|
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
||||||
},
|
},
|
||||||
onbeforeremove: vnode => {
|
onbeforeremove: vnode => {
|
||||||
vnode.dom.style.opacity = 0;
|
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -312,7 +315,7 @@ function renderNotifications(notifs) {
|
|||||||
return notificationElements;
|
return notificationElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function component() {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
const queue = taskQueue.getQueue();
|
const queue = taskQueue.getQueue();
|
||||||
@@ -324,21 +327,26 @@ export default function component() {
|
|||||||
const stagingElements = renderStaging(staging);
|
const stagingElements = renderStaging(staging);
|
||||||
const queueElements = renderQueue(queue);
|
const queueElements = renderQueue(queue);
|
||||||
|
|
||||||
function repositionNotifications() {
|
function repositionNotifications(): void {
|
||||||
let top = 10;
|
let top = 10;
|
||||||
for (const c of notificationElements) {
|
for (const c of notificationElements as VnodeDOM[]) {
|
||||||
c.dom.style.top = top;
|
(c.dom as HTMLDivElement).style.top = `${top}`;
|
||||||
top += c.dom.offsetHeight + 10;
|
top += (c.dom as HTMLDivElement).offsetHeight + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeDrawer() {
|
function resizeDrawer(): void {
|
||||||
let height =
|
let height =
|
||||||
statusElement.dom.offsetTop + statusElement.dom.offsetHeight;
|
statusElement.dom.offsetTop + statusElement.dom.offsetHeight;
|
||||||
if (stagingElements.length) {
|
if (stagingElements.length) {
|
||||||
for (const s of stagingElements)
|
for (const s of stagingElements as VnodeDOM[]) {
|
||||||
height = Math.max(height, s.dom.offsetTop + s.dom.offsetHeight);
|
height = Math.max(
|
||||||
} else if (vnode.state.mouseIn) {
|
height,
|
||||||
|
(s.dom as HTMLDivElement).offsetTop +
|
||||||
|
(s.dom as HTMLDivElement).offsetHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (vnode.state["mouseIn"]) {
|
||||||
for (const c of drawerElement.children)
|
for (const c of drawerElement.children)
|
||||||
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
|
height = Math.max(height, c.dom.offsetTop + c.dom.offsetHeight);
|
||||||
}
|
}
|
||||||
@@ -347,7 +355,7 @@ export default function component() {
|
|||||||
|
|
||||||
if (stagingElements.length + queueElements.length) {
|
if (stagingElements.length + queueElements.length) {
|
||||||
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
|
const statusCount = { queued: 0, pending: 0, fault: 0, stale: 0 };
|
||||||
for (const t of queue) statusCount[t.status] += 1;
|
for (const t of queue) statusCount[t["status"]] += 1;
|
||||||
|
|
||||||
const actions = m(
|
const actions = m(
|
||||||
".actions",
|
".actions",
|
||||||
@@ -358,7 +366,7 @@ export default function 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(
|
||||||
@@ -451,25 +459,26 @@ export default function 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.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.onmouseover = vnode2.dom.onmouseleave = null;
|
(vnode2.dom as HTMLDivElement).onmouseover = null;
|
||||||
vnode2.dom.style.opacity = 0;
|
(vnode2.dom as HTMLDivElement).onmouseleave = null;
|
||||||
vnode2.dom.style.height = 0;
|
(vnode2.dom as HTMLDivElement).style.opacity = "0";
|
||||||
|
(vnode2.dom as HTMLDivElement).style.height = "0";
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
});
|
});
|
||||||
@@ -496,4 +505,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
"use strict";
|
export let codeMirror;
|
||||||
|
export let yaml;
|
||||||
|
|
||||||
let codeMirror;
|
export function loadCodeMirror(): Promise<void> {
|
||||||
let yaml;
|
|
||||||
|
|
||||||
function loadCodeMirror() {
|
|
||||||
if (codeMirror) return Promise.resolve();
|
if (codeMirror) return Promise.resolve();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -20,7 +18,7 @@ function loadCodeMirror() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadYaml() {
|
export function loadYaml(): Promise<void> {
|
||||||
if (yaml) return Promise.resolve();
|
if (yaml) return Promise.resolve();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -32,5 +30,3 @@ function loadYaml() {
|
|||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { loadCodeMirror, codeMirror, loadYaml, yaml };
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { m } from "./components";
|
|
||||||
|
|
||||||
export function component() {
|
|
||||||
return {
|
|
||||||
view: function(vnode) {
|
|
||||||
document.title = "Error! - GenieACS";
|
|
||||||
return m("p.error", vnode.attrs.error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
import { m } from "./components";
|
||||||
|
|
||||||
|
export const component: ClosureComponent = (): Component => {
|
||||||
|
return {
|
||||||
|
view: function(vnode) {
|
||||||
|
document.title = "Error! - GenieACS";
|
||||||
|
return m("p.error", vnode.attrs["error"]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import filterComponent from "./filter-component";
|
import filterComponent from "./filter-component";
|
||||||
@@ -46,7 +45,7 @@ const unpackSmartQuery = memoize(query => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
if (!window.authorizer.hasAccess("faults", 2)) {
|
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")
|
||||||
@@ -72,7 +71,7 @@ function renderTable(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
sort,
|
sort,
|
||||||
onSortChange
|
onSortChange
|
||||||
) {
|
): Children {
|
||||||
const faults = faultsResponse.value;
|
const faults = faultsResponse.value;
|
||||||
const selectAll = m("input", {
|
const selectAll = m("input", {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
@@ -229,57 +228,60 @@ function renderTable(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Faults - GenieACS";
|
document.title = "Faults - GenieACS";
|
||||||
|
|
||||||
function showMore() {
|
function showMore(): void {
|
||||||
vnode.state.showCount =
|
vnode.state["showCount"] =
|
||||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChanged(filter) {
|
function onFilterChanged(filter): void {
|
||||||
const ops = { filter };
|
const ops = { filter };
|
||||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||||
m.route.set("/faults", ops);
|
m.route.set("/faults", ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sort) {
|
function onSortChange(sort): void {
|
||||||
const ops = { sort };
|
const ops = { sort };
|
||||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/faults", ops);
|
m.route.set("/faults", ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs["sort"]
|
||||||
let filter = vnode.attrs.filter
|
? memoizedJsonParse(vnode.attrs["sort"])
|
||||||
? memoizedParse(vnode.attrs.filter)
|
: {};
|
||||||
|
let filter = vnode.attrs["filter"]
|
||||||
|
? memoizedParse(vnode.attrs["filter"])
|
||||||
: true;
|
: true;
|
||||||
filter = unpackSmartQuery(filter);
|
filter = unpackSmartQuery(filter);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
const selected = new Set();
|
const selected = new Set();
|
||||||
if (vnode.state.selected) {
|
if (vnode.state["selected"]) {
|
||||||
for (const f of faults.value)
|
for (const f of faults.value)
|
||||||
if (vnode.state.selected.has(f["_id"])) selected.add(f["_id"]);
|
if (vnode.state["selected"].has(f["_id"])) selected.add(f["_id"]);
|
||||||
}
|
}
|
||||||
vnode.state.selected = selected;
|
vnode.state["selected"] = selected;
|
||||||
|
|
||||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["resource"] = "faults";
|
||||||
|
attrs["filter"] = vnode.attrs["filter"];
|
||||||
|
attrs["onChange"] = onFilterChanged;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("h1", "Listing faults"),
|
m("h1", "Listing faults"),
|
||||||
m(filterComponent, {
|
m(filterComponent, attrs),
|
||||||
resource: "faults",
|
|
||||||
filter: vnode.attrs.filter,
|
|
||||||
onChange: onFilterChanged
|
|
||||||
}),
|
|
||||||
renderTable(
|
renderTable(
|
||||||
faults,
|
faults,
|
||||||
count.value,
|
count.value,
|
||||||
@@ -292,4 +294,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import filterComponent from "./filter-component";
|
import filterComponent from "./filter-component";
|
||||||
@@ -16,7 +15,12 @@ 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 attributes = [
|
const attributes: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
options?: any;
|
||||||
|
}[] = [
|
||||||
{ id: "_id", label: "Name" },
|
{ id: "_id", label: "Name" },
|
||||||
{
|
{
|
||||||
id: "metadata.fileType",
|
id: "metadata.fileType",
|
||||||
@@ -50,7 +54,11 @@ const unpackSmartQuery = memoize(query => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function putActionHandler(action, _object) {
|
interface ValidationErrors {
|
||||||
|
[prop: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function putActionHandler(action, _object): Promise<ValidationErrors> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const object = Object.assign({}, _object);
|
const object = Object.assign({}, _object);
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
@@ -109,7 +117,7 @@ const getDownloadUrl = memoize(filter => {
|
|||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
if (!window.authorizer.hasAccess("files", 2)) {
|
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")
|
||||||
@@ -129,7 +137,7 @@ function renderTable(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
sort,
|
sort,
|
||||||
onSortChange
|
onSortChange
|
||||||
) {
|
): Children {
|
||||||
const files = filesResponse.value;
|
const files = filesResponse.value;
|
||||||
const selectAll = m("input", {
|
const selectAll = m("input", {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
@@ -270,7 +278,7 @@ function renderTable(
|
|||||||
{
|
{
|
||||||
title: "Create new file",
|
title: "Create new file",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -279,9 +287,11 @@ function renderTable(
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object)
|
putActionHandler(action, object)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -318,58 +328,63 @@ function renderTable(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Files - GenieACS";
|
document.title = "Files - GenieACS";
|
||||||
|
|
||||||
function showMore() {
|
function showMore(): void {
|
||||||
vnode.state.showCount =
|
vnode.state["showCount"] =
|
||||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChanged(filter) {
|
function onFilterChanged(filter): void {
|
||||||
const ops = { filter };
|
const ops = { filter };
|
||||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sort) {
|
function onSortChange(sort): void {
|
||||||
const ops = { sort };
|
const ops = { sort };
|
||||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs["sort"]
|
||||||
let filter = vnode.attrs.filter
|
? memoizedJsonParse(vnode.attrs["sort"])
|
||||||
? memoizedParse(vnode.attrs.filter)
|
: {};
|
||||||
|
let filter = vnode.attrs["filter"]
|
||||||
|
? memoizedParse(vnode.attrs["filter"])
|
||||||
: true;
|
: true;
|
||||||
filter = unpackSmartQuery(filter);
|
filter = unpackSmartQuery(filter);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
const selected = new Set();
|
const selected = new Set();
|
||||||
if (vnode.state.selected) {
|
if (vnode.state["selected"]) {
|
||||||
for (const file of files.value)
|
for (const file of files.value) {
|
||||||
if (vnode.state.selected.has(file["_id"])) selected.add(file["_id"]);
|
if (vnode.state["selected"].has(file["_id"]))
|
||||||
|
selected.add(file["_id"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vnode.state.selected = selected;
|
vnode.state["selected"] = selected;
|
||||||
|
|
||||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["resource"] = "files";
|
||||||
|
attrs["filter"] = vnode.attrs["filter"];
|
||||||
|
attrs["onChange"] = onFilterChanged;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("h1", "Listing files"),
|
m("h1", "Listing files"),
|
||||||
m(filterComponent, {
|
m(filterComponent, attrs),
|
||||||
resource: "files",
|
|
||||||
filter: vnode.attrs.filter,
|
|
||||||
onChange: onFilterChanged
|
|
||||||
}),
|
|
||||||
renderTable(
|
renderTable(
|
||||||
files,
|
files,
|
||||||
count.value,
|
count.value,
|
||||||
@@ -382,4 +397,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
"use strict";
|
import m, { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
|
||||||
import { parse, stringify, map } from "../lib/common/expression-parser";
|
import { parse, stringify, map } from "../lib/common/expression-parser";
|
||||||
import memoize from "../lib/common/memoize";
|
import memoize from "../lib/common/memoize";
|
||||||
import Autocomplete from "./autocomplete-compnent";
|
import Autocomplete from "./autocomplete-compnent";
|
||||||
import * as smartQuery from "./smart-query";
|
import * as smartQuery from "./smart-query";
|
||||||
import { filterToMongoQuery } from "../lib/ui/mongodb-functions";
|
import { filterToMongoQuery } from "../lib/ui/mongodb-functions";
|
||||||
|
import { Expression } from "../lib/types";
|
||||||
|
|
||||||
const getAutocomplete = memoize(resource => {
|
const getAutocomplete = memoize(resource => {
|
||||||
const labels = smartQuery.getLabels(resource);
|
const labels = smartQuery.getLabels(resource);
|
||||||
@@ -16,7 +15,7 @@ const getAutocomplete = memoize(resource => {
|
|||||||
return autocomplete;
|
return autocomplete;
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseFilter(resource, f) {
|
function parseFilter(resource, f): Expression {
|
||||||
let exp;
|
let exp;
|
||||||
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
||||||
const k = f.split(":", 1)[0];
|
const k = f.split(":", 1)[0];
|
||||||
@@ -38,7 +37,7 @@ function parseFilter(resource, f) {
|
|||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyFilter(f) {
|
function stringifyFilter(f: Expression): string {
|
||||||
if (Array.isArray(f) && f[0] === "FUNC" && f[1] === "Q")
|
if (Array.isArray(f) && f[0] === "FUNC" && f[1] === "Q")
|
||||||
return `${f[2]}: ${f[3]}`;
|
return `${f[2]}: ${f[3]}`;
|
||||||
return stringify(f);
|
return stringify(f);
|
||||||
@@ -56,39 +55,39 @@ const splitFilter = memoize(filter => {
|
|||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function 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"]
|
||||||
) {
|
) {
|
||||||
vnode.state.filterInvalid = 0;
|
vnode.state["filterInvalid"] = 0;
|
||||||
vnode.state.filter = vnode.attrs.filter;
|
vnode.state["filter"] = vnode.attrs["filter"];
|
||||||
vnode.state.filterList = splitFilter(vnode.attrs.filter);
|
vnode.state["filterList"] = splitFilter(vnode.attrs["filter"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChange() {
|
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);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vnode.state.filterInvalid |= 1 << idx;
|
vnode.state["filterInvalid"] |= 1 << idx;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
vnode.state.filterList.push("");
|
vnode.state["filterList"].push("");
|
||||||
|
|
||||||
if (!vnode.state.filterInvalid) {
|
if (!vnode.state["filterInvalid"]) {
|
||||||
delete vnode.state.filter;
|
delete vnode.state["filter"];
|
||||||
if (filter.length === 0) {
|
if (filter.length === 0) {
|
||||||
vnode.attrs.onChange("");
|
vnode.attrs["onChange"]("");
|
||||||
} else {
|
} else {
|
||||||
if (filter.length > 1) filter = ["AND"].concat(filter);
|
if (filter.length > 1) filter = ["AND"].concat(filter);
|
||||||
else filter = filter[0];
|
else filter = filter[0];
|
||||||
vnode.attrs.onChange(stringify(filter));
|
vnode.attrs["onChange"](stringify(filter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,18 +95,20 @@ export default function component() {
|
|||||||
return m(
|
return m(
|
||||||
"div.filter",
|
"div.filter",
|
||||||
[m("b", "Filter")].concat(
|
[m("b", "Filter")].concat(
|
||||||
vnode.state.filterList.map((fltr, idx) => {
|
vnode.state["filterList"].map((fltr, idx) => {
|
||||||
return m("input", {
|
return m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
|
class: `${
|
||||||
|
(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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -115,4 +116,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
"use strict";
|
import m, { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
|
||||||
import menu from "./menu";
|
import menu from "./menu";
|
||||||
import drawerComponent from "./drawer-component";
|
import drawerComponent from "./drawer-component";
|
||||||
import userMenu from "./user-menu";
|
import userMenu from "./user-menu";
|
||||||
@@ -15,29 +13,39 @@ const adminPages = [
|
|||||||
"config"
|
"config"
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function layout() {
|
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"])) {
|
||||||
group = "admin";
|
group = "admin";
|
||||||
sideMenu = m(adminMenu, { page: vnode.attrs.page });
|
const attrs = {};
|
||||||
|
attrs["page"] = vnode.attrs["page"];
|
||||||
|
sideMenu = m(adminMenu, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["page"] = group || vnode.attrs["page"];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("#header", [
|
m("#header", [
|
||||||
m("img.logo", { src: "logo.svg" }),
|
m("img.logo", { src: "logo.svg" }),
|
||||||
m(userMenu),
|
m(userMenu),
|
||||||
m(menu, { page: group || vnode.attrs.page }),
|
m(menu, attrs),
|
||||||
m(drawerComponent)
|
m(drawerComponent)
|
||||||
]),
|
]),
|
||||||
m(
|
m(
|
||||||
"#content-wrapper",
|
"#content-wrapper",
|
||||||
sideMenu,
|
sideMenu,
|
||||||
m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
|
m("#content", { class: `page-${vnode.attrs["page"]}` }, [
|
||||||
|
vnode.children
|
||||||
|
])
|
||||||
),
|
),
|
||||||
overlay.render()
|
overlay.render()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
return Promise.resolve(args);
|
return Promise.resolve(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function 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";
|
||||||
return [
|
return [
|
||||||
@@ -24,11 +23,11 @@ export function component() {
|
|||||||
m("input", {
|
m("input", {
|
||||||
name: "username",
|
name: "username",
|
||||||
type: "text",
|
type: "text",
|
||||||
value: vnode.state.username,
|
value: vnode.state["username"],
|
||||||
oncreate: vnode2 => {
|
oncreate: vnode2 => {
|
||||||
vnode2.dom.focus();
|
(vnode2.dom as HTMLInputElement).focus();
|
||||||
},
|
},
|
||||||
oninput: m.withAttr("value", v => (vnode.state.username = v))
|
oninput: m.withAttr("value", v => (vnode.state["username"] = v))
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m(
|
m(
|
||||||
@@ -37,8 +36,8 @@ export function component() {
|
|||||||
m("input", {
|
m("input", {
|
||||||
name: "password",
|
name: "password",
|
||||||
type: "password",
|
type: "password",
|
||||||
value: vnode.state.password,
|
value: vnode.state["password"],
|
||||||
oninput: m.withAttr("value", v => (vnode.state.password = v))
|
oninput: m.withAttr("value", v => (vnode.state["password"] = v))
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m(
|
m(
|
||||||
@@ -50,7 +49,7 @@ export function component() {
|
|||||||
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();
|
||||||
})
|
})
|
||||||
@@ -68,4 +67,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
"use strict";
|
import m, { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
|
||||||
import * as overlay from "./overlay";
|
import * as overlay from "./overlay";
|
||||||
|
|
||||||
export default function 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) {
|
function overflowed(_vnode): void {
|
||||||
if (_vnode.dom.classList.contains("long-text-overflowed")) return;
|
if (_vnode.dom.classList.contains("long-text-overflowed")) return;
|
||||||
_vnode.dom.classList.add("long-text-overflowed");
|
_vnode.dom.classList.add("long-text-overflowed");
|
||||||
_vnode.dom.onclick = e => {
|
_vnode.dom.onclick = e => {
|
||||||
@@ -20,8 +18,8 @@ export default function component() {
|
|||||||
rows: 24,
|
rows: 24,
|
||||||
readonly: "",
|
readonly: "",
|
||||||
oncreate: vnode2 => {
|
oncreate: vnode2 => {
|
||||||
vnode2.dom.focus();
|
(vnode2.dom as HTMLTextAreaElement).focus();
|
||||||
vnode2.dom.select();
|
(vnode2.dom as HTMLTextAreaElement).select();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -40,8 +38,10 @@ export default function component() {
|
|||||||
},
|
},
|
||||||
onupdate: vnode2 => {
|
onupdate: vnode2 => {
|
||||||
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
|
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
|
||||||
vnode2.dom.classList.remove("long-text-overflowed");
|
(vnode2.dom as HTMLElement).classList.remove(
|
||||||
vnode2.dom.onclick = null;
|
"long-text-overflowed"
|
||||||
|
);
|
||||||
|
(vnode2.dom as HTMLElement).onclick = null;
|
||||||
} else {
|
} else {
|
||||||
overflowed(vnode2);
|
overflowed(vnode2);
|
||||||
}
|
}
|
||||||
@@ -53,4 +53,6 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
"use strict";
|
import m, { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
const component: ClosureComponent = (): Component => {
|
||||||
|
|
||||||
export default function menu() {
|
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
const active = { [vnode.attrs.page]: "active" };
|
const active = { [vnode.attrs["page"]]: "active" };
|
||||||
|
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
if (window.authorizer.hasAccess("devices", 1)) {
|
if (window.authorizer.hasAccess("devices", 1)) {
|
||||||
@@ -60,4 +58,6 @@ export default function menu() {
|
|||||||
return m("nav", m("ul", tabs));
|
return m("nav", m("ul", tabs));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import m from "mithril";
|
import m from "mithril";
|
||||||
|
|
||||||
const notifications = new Set();
|
interface Notification {
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function push(type, message) {
|
const notifications = new Set<Notification>();
|
||||||
|
|
||||||
|
export function push(type, message): void {
|
||||||
const n = { type: type, message: message, timestamp: Date.now() };
|
const n = { type: type, message: message, timestamp: Date.now() };
|
||||||
notifications.add(n);
|
notifications.add(n);
|
||||||
m.redraw();
|
m.redraw();
|
||||||
@@ -14,6 +18,6 @@ export function push(type, message) {
|
|||||||
}, 4000);
|
}, 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNotifications() {
|
export function getNotifications(): Set<Notification> {
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
"use strict";
|
import m, { Children } from "mithril";
|
||||||
|
|
||||||
import m from "mithril";
|
type OverlayCallback = () => Children;
|
||||||
|
|
||||||
let overlayCallback = null;
|
let overlayCallback: OverlayCallback = null;
|
||||||
|
|
||||||
function open(callback) {
|
export function open(callback: OverlayCallback): void {
|
||||||
overlayCallback = callback;
|
overlayCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(callback) {
|
export function close(callback: OverlayCallback): boolean {
|
||||||
if (callback === overlayCallback) {
|
if (callback === overlayCallback) {
|
||||||
overlayCallback = null;
|
overlayCallback = null;
|
||||||
return true;
|
return true;
|
||||||
@@ -17,7 +17,7 @@ function close(callback) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
export function render(): Children {
|
||||||
if (overlayCallback) {
|
if (overlayCallback) {
|
||||||
return m(
|
return m(
|
||||||
".overlay-wrapper",
|
".overlay-wrapper",
|
||||||
@@ -28,11 +28,11 @@ function render() {
|
|||||||
},
|
},
|
||||||
style: "opacity: 0",
|
style: "opacity: 0",
|
||||||
oncreate: vnode => {
|
oncreate: vnode => {
|
||||||
vnode.dom.focus();
|
(vnode.dom as HTMLDivElement).focus();
|
||||||
vnode.dom.style.opacity = 1;
|
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
||||||
},
|
},
|
||||||
onbeforeremove: vnode => {
|
onbeforeremove: vnode => {
|
||||||
vnode.dom.style.opacity = 0;
|
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -62,5 +62,3 @@ document.addEventListener("keydown", e => {
|
|||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("popstate", () => {
|
||||||
if (close(overlayCallback)) m.redraw();
|
if (close(overlayCallback)) m.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
export { open, close, render };
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
@@ -8,25 +7,25 @@ import pieChartComponent from "./pie-chart-component";
|
|||||||
const GROUPS = config.ui.overview.groups;
|
const GROUPS = config.ui.overview.groups;
|
||||||
const CHARTS = {};
|
const CHARTS = {};
|
||||||
for (const group of Object.values(GROUPS)) {
|
for (const group of Object.values(GROUPS)) {
|
||||||
for (const chartName of Object.values(group["charts"]))
|
for (const chartName of Object.values(group["charts"]) as string[])
|
||||||
CHARTS[chartName] = config.ui.overview.charts[chartName];
|
CHARTS[chartName] = config.ui.overview.charts[chartName];
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryCharts(charts) {
|
function queryCharts(charts): {} {
|
||||||
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);
|
||||||
chart.slices = Object.assign({}, chart.slices);
|
chart["slices"] = Object.assign({}, chart["slices"]);
|
||||||
for (let [sliceName, slice] of Object.entries(chart.slices)) {
|
for (let [sliceName, slice] of Object.entries(chart["slices"])) {
|
||||||
const filter = slice.filter;
|
const filter = slice["filter"];
|
||||||
chart.slices[sliceName] = slice = Object.assign({}, slice);
|
chart["slices"][sliceName] = slice = Object.assign({}, slice);
|
||||||
slice.count = store.count("devices", filter);
|
slice["count"] = store.count("devices", filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return charts;
|
return charts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init(): Promise<{}> {
|
||||||
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")
|
||||||
@@ -36,21 +35,23 @@ export function init() {
|
|||||||
return Promise.resolve({ charts: queryCharts(CHARTS) });
|
return Promise.resolve({ charts: queryCharts(CHARTS) });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function 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)) {
|
||||||
if (group.label) children.push(m("h1", group.label));
|
if (group["label"]) children.push(m("h1", group["label"]));
|
||||||
|
|
||||||
const groupChildren = [];
|
const groupChildren = [];
|
||||||
for (const chartName of Object.values(group.charts)) {
|
for (const chartName of Object.values(group["charts"]) as string[]) {
|
||||||
const chart = vnode.attrs.charts[chartName];
|
const chart = vnode.attrs["charts"][chartName];
|
||||||
const chartChildren = [];
|
const chartChildren = [];
|
||||||
if (chart.label) chartChildren.push(m("h2", chart.label));
|
if (chart.label) chartChildren.push(m("h2", chart.label));
|
||||||
|
|
||||||
chartChildren.push(m(pieChartComponent, { chart: chart }));
|
const attrs = {};
|
||||||
|
attrs["chart"] = chart;
|
||||||
|
chartChildren.push(m(pieChartComponent, attrs));
|
||||||
|
|
||||||
groupChildren.push(m(".overview-chart", chartChildren));
|
groupChildren.push(m(".overview-chart", chartChildren));
|
||||||
}
|
}
|
||||||
@@ -61,4 +62,4 @@ export function component() {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import { stringify } from "../lib/common/expression-parser";
|
import { stringify } from "../lib/common/expression-parser";
|
||||||
import memoize from "../lib/common/memoize";
|
import memoize from "../lib/common/memoize";
|
||||||
|
|
||||||
const memoizedStringify = memoize(stringify);
|
const memoizedStringify = memoize(stringify);
|
||||||
|
|
||||||
const drawChart = function(chartData) {
|
function drawChart(chartData): Children {
|
||||||
const slices = chartData.slices;
|
const slices = chartData.slices;
|
||||||
const total = Array.from(Object.values(chartData.slices)).reduce(
|
const total: number = Array.from(Object.values(chartData.slices)).reduce(
|
||||||
(a, s) => a + (s.count.value || 0),
|
(a: number, s) => a + (s["count"]["value"] || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const legend = [];
|
const legend = [];
|
||||||
@@ -21,21 +20,21 @@ const drawChart = function(chartData) {
|
|||||||
let endX, endY;
|
let endX, endY;
|
||||||
|
|
||||||
for (const slice of Object.values(slices)) {
|
for (const slice of Object.values(slices)) {
|
||||||
const percent = total > 0 ? (slice.count.value || 0) / total : 0;
|
const percent = total > 0 ? (slice["count"]["value"] || 0) / total : 0;
|
||||||
legend.push(
|
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)}%)`
|
||||||
])
|
])
|
||||||
@@ -58,7 +57,7 @@ const drawChart = function(chartData) {
|
|||||||
paths.push(
|
paths.push(
|
||||||
m("path", {
|
m("path", {
|
||||||
d: sketch,
|
d: sketch,
|
||||||
fill: slice.color
|
fill: slice["color"]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ const drawChart = function(chartData) {
|
|||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
"xlink:href": `/#!/devices/?${m.buildQueryString({
|
"xlink:href": `/#!/devices/?${m.buildQueryString({
|
||||||
filter: memoizedStringify(slice.filter)
|
filter: memoizedStringify(slice["filter"])
|
||||||
})}`
|
})}`
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -113,12 +112,14 @@ const drawChart = function(chartData) {
|
|||||||
),
|
),
|
||||||
m(".legend", legend)
|
m(".legend", legend)
|
||||||
]);
|
]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function component() {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
return drawChart(vnode.attrs.chart);
|
return drawChart(vnode.attrs["chart"]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import filterComponent from "./filter-component";
|
import filterComponent from "./filter-component";
|
||||||
@@ -35,7 +34,11 @@ const unpackSmartQuery = memoize(query => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function putActionHandler(action, _object, isNew) {
|
interface ValidationErrors {
|
||||||
|
[prop: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const object = Object.assign({}, _object);
|
const object = Object.assign({}, _object);
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
@@ -45,7 +48,7 @@ function putActionHandler(action, _object, isNew) {
|
|||||||
const errors = {};
|
const errors = {};
|
||||||
|
|
||||||
if (!id) errors["_id"] = "ID can not be empty";
|
if (!id) errors["_id"] = "ID can not be empty";
|
||||||
if (!object.provision) errors.provision = "Provision not selected";
|
if (!object.provision) errors["provision"] = "Provision not selected";
|
||||||
|
|
||||||
if (Object.keys(errors).length) return void resolve(errors);
|
if (Object.keys(errors).length) return void resolve(errors);
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ const getDownloadUrl = memoize(filter => {
|
|||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
if (!window.authorizer.hasAccess("presets", 2)) {
|
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")
|
||||||
@@ -127,7 +130,7 @@ function renderTable(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
sort,
|
sort,
|
||||||
onSortChange
|
onSortChange
|
||||||
) {
|
): Children {
|
||||||
const presets = presetsResponse.value;
|
const presets = presetsResponse.value;
|
||||||
const selectAll = m("input", {
|
const selectAll = m("input", {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
@@ -215,7 +218,7 @@ function renderTable(
|
|||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -225,9 +228,11 @@ function renderTable(
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, false)
|
putActionHandler(action, object, false)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -334,7 +339,7 @@ function renderTable(
|
|||||||
{
|
{
|
||||||
title: "Create new preset",
|
title: "Create new preset",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -343,9 +348,11 @@ function renderTable(
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, true)
|
putActionHandler(action, object, true)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -382,37 +389,39 @@ function renderTable(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Presets - GenieACS";
|
document.title = "Presets - GenieACS";
|
||||||
|
|
||||||
function showMore() {
|
function showMore(): void {
|
||||||
vnode.state.showCount =
|
vnode.state["showCount"] =
|
||||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChanged(filter) {
|
function onFilterChanged(filter): void {
|
||||||
const ops = { filter };
|
const ops = { filter };
|
||||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sort) {
|
function onSortChange(sort): void {
|
||||||
const ops = { sort };
|
const ops = { sort };
|
||||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs["sort"]
|
||||||
let filter = vnode.attrs.filter
|
? memoizedJsonParse(vnode.attrs["sort"])
|
||||||
? memoizedParse(vnode.attrs.filter)
|
: {};
|
||||||
|
let filter = vnode.attrs["filter"]
|
||||||
|
? memoizedParse(vnode.attrs["filter"])
|
||||||
: true;
|
: true;
|
||||||
filter = unpackSmartQuery(filter);
|
filter = unpackSmartQuery(filter);
|
||||||
|
|
||||||
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);
|
||||||
@@ -435,27 +444,28 @@ export function component() {
|
|||||||
|
|
||||||
for (const p of provisions.value) provisionIds.add(p["_id"]);
|
for (const p of provisions.value) provisionIds.add(p["_id"]);
|
||||||
|
|
||||||
provisionAttr.options = Array.from(provisionIds);
|
provisionAttr["options"] = Array.from(provisionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = new Set();
|
const selected = new Set();
|
||||||
if (vnode.state.selected) {
|
if (vnode.state["selected"]) {
|
||||||
for (const preset of presets.value) {
|
for (const preset of presets.value) {
|
||||||
if (vnode.state.selected.has(preset["_id"]))
|
if (vnode.state["selected"].has(preset["_id"]))
|
||||||
selected.add(preset["_id"]);
|
selected.add(preset["_id"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vnode.state.selected = selected;
|
vnode.state["selected"] = selected;
|
||||||
|
|
||||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["resource"] = "presets";
|
||||||
|
attrs["filter"] = vnode.attrs["filter"];
|
||||||
|
attrs["onChange"] = onFilterChanged;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("h1", "Listing presets"),
|
m("h1", "Listing presets"),
|
||||||
m(filterComponent, {
|
m(filterComponent, attrs),
|
||||||
resource: "presets",
|
|
||||||
filter: vnode.attrs.filter,
|
|
||||||
onChange: onFilterChanged
|
|
||||||
}),
|
|
||||||
renderTable(
|
renderTable(
|
||||||
presets,
|
presets,
|
||||||
count.value,
|
count.value,
|
||||||
@@ -468,4 +478,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import filterComponent from "./filter-component";
|
import filterComponent from "./filter-component";
|
||||||
@@ -30,7 +29,11 @@ const unpackSmartQuery = memoize(query => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function putActionHandler(action, _object, isNew) {
|
interface ValidationErrors {
|
||||||
|
[prop: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const object = Object.assign({}, _object);
|
const object = Object.assign({}, _object);
|
||||||
if (action === "save") {
|
if (action === "save") {
|
||||||
@@ -97,7 +100,7 @@ const getDownloadUrl = memoize(filter => {
|
|||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(args) {
|
export function init(args): Promise<{}> {
|
||||||
if (!window.authorizer.hasAccess("provisions", 2)) {
|
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")
|
||||||
@@ -124,7 +127,7 @@ function renderTable(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
sort,
|
sort,
|
||||||
onSortChange
|
onSortChange
|
||||||
) {
|
): Children {
|
||||||
const provisions = provisionsResponse.value;
|
const provisions = provisionsResponse.value;
|
||||||
const selectAll = m("input", {
|
const selectAll = m("input", {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
@@ -197,7 +200,7 @@ function renderTable(
|
|||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -207,9 +210,11 @@ function renderTable(
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, false)
|
putActionHandler(action, object, false)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -322,7 +327,7 @@ function renderTable(
|
|||||||
{
|
{
|
||||||
title: "Create new provision",
|
title: "Create new provision",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const cb = () => {
|
const cb = (): Children => {
|
||||||
return m(
|
return m(
|
||||||
putFormComponent,
|
putFormComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -331,9 +336,11 @@ function renderTable(
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, true)
|
putActionHandler(action, object, true)
|
||||||
.then(errors => {
|
.then(errors => {
|
||||||
errors = errors ? Object.values(errors) : [];
|
const errorList = errors
|
||||||
if (errors.length) {
|
? Object.values(errors)
|
||||||
for (const err of errors)
|
: [];
|
||||||
|
if (errorList.length) {
|
||||||
|
for (const err of errorList)
|
||||||
notifications.push("error", err);
|
notifications.push("error", err);
|
||||||
} else {
|
} else {
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
@@ -370,60 +377,63 @@ function renderTable(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component() {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
document.title = "Provisions - GenieACS";
|
document.title = "Provisions - GenieACS";
|
||||||
|
|
||||||
function showMore() {
|
function showMore(): void {
|
||||||
vnode.state.showCount =
|
vnode.state["showCount"] =
|
||||||
(vnode.state.showCount || PAGE_SIZE) + PAGE_SIZE;
|
(vnode.state["showCount"] || PAGE_SIZE) + PAGE_SIZE;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChanged(filter) {
|
function onFilterChanged(filter): void {
|
||||||
const ops = { filter };
|
const ops = { filter };
|
||||||
if (vnode.attrs.sort) ops.sort = vnode.attrs.sort;
|
if (vnode.attrs["sort"]) ops["sort"] = vnode.attrs["sort"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sort) {
|
function onSortChange(sort): void {
|
||||||
const ops = { sort };
|
const ops = { sort };
|
||||||
if (vnode.attrs.filter) ops.filter = vnode.attrs.filter;
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set(m.route.get(), ops);
|
m.route.set(m.route.get(), ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};
|
const sort = vnode.attrs["sort"]
|
||||||
let filter = vnode.attrs.filter
|
? memoizedJsonParse(vnode.attrs["sort"])
|
||||||
? memoizedParse(vnode.attrs.filter)
|
: {};
|
||||||
|
let filter = vnode.attrs["filter"]
|
||||||
|
? memoizedParse(vnode.attrs["filter"])
|
||||||
: true;
|
: true;
|
||||||
filter = unpackSmartQuery(filter);
|
filter = unpackSmartQuery(filter);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
const selected = new Set();
|
const selected = new Set();
|
||||||
if (vnode.state.selected) {
|
if (vnode.state["selected"]) {
|
||||||
for (const provision of provisions.value) {
|
for (const provision of provisions.value) {
|
||||||
if (vnode.state.selected.has(provision["_id"]))
|
if (vnode.state["selected"].has(provision["_id"]))
|
||||||
selected.add(provision["_id"]);
|
selected.add(provision["_id"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vnode.state.selected = selected;
|
vnode.state["selected"] = selected;
|
||||||
|
|
||||||
const downloadUrl = getDownloadUrl(vnode.attrs.filter);
|
const downloadUrl = getDownloadUrl(vnode.attrs["filter"]);
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
attrs["resource"] = "provisions";
|
||||||
|
attrs["filter"] = vnode.attrs["filter"];
|
||||||
|
attrs["onChange"] = onFilterChanged;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("h1", "Listing provisions"),
|
m("h1", "Listing provisions"),
|
||||||
m(filterComponent, {
|
m(filterComponent, attrs),
|
||||||
resource: "provisions",
|
|
||||||
filter: vnode.attrs.filter,
|
|
||||||
onChange: onFilterChanged
|
|
||||||
}),
|
|
||||||
renderTable(
|
renderTable(
|
||||||
provisions,
|
provisions,
|
||||||
count.value,
|
count.value,
|
||||||
@@ -436,4 +446,4 @@ export function component() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
import { VnodeDOM, ClosureComponent, Component, Children } from "mithril";
|
||||||
|
|
||||||
import { m } from "./components";
|
import { m } from "./components";
|
||||||
import { codeMirror } from "./dynamic-loader";
|
import { codeMirror } from "./dynamic-loader";
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ const singular = {
|
|||||||
files: "file"
|
files: "file"
|
||||||
};
|
};
|
||||||
|
|
||||||
function createField(current, attr, focus) {
|
function createField(current, attr, focus): Children {
|
||||||
if (attr.type === "combo") {
|
if (attr.type === "combo") {
|
||||||
const options = [m("option", { value: "" }, "--Please choose--")];
|
const options = [m("option", { value: "" }, "--Please choose--")];
|
||||||
for (const op of attr.options) options.push(m("option", { value: op }, op));
|
for (const op of attr.options) options.push(m("option", { value: op }, op));
|
||||||
@@ -25,7 +24,7 @@ function createField(current, attr, focus) {
|
|||||||
value: selected,
|
value: selected,
|
||||||
oncreate: focus
|
oncreate: focus
|
||||||
? _vnode => {
|
? _vnode => {
|
||||||
_vnode.dom.focus();
|
(_vnode.dom as HTMLSelectElement).focus();
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
onchange: e => {
|
onchange: e => {
|
||||||
@@ -45,10 +44,14 @@ function createField(current, attr, focus) {
|
|||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
"Ctrl-Enter": () => {
|
"Ctrl-Enter": () => {
|
||||||
_vnode.dom.form.querySelector("button[type=submit]").click();
|
((_vnode.dom as HTMLTextAreaElement).form.querySelector(
|
||||||
|
"button[type=submit]"
|
||||||
|
) as HTMLButtonElement).click();
|
||||||
},
|
},
|
||||||
"Cmd-Enter": () => {
|
"Cmd-Enter": () => {
|
||||||
_vnode.dom.form.querySelector("button[type=submit]").click();
|
((_vnode.dom as HTMLTextAreaElement).form.querySelector(
|
||||||
|
"button[type=submit]"
|
||||||
|
) as HTMLButtonElement).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -66,7 +69,7 @@ function createField(current, attr, focus) {
|
|||||||
name: attr.id,
|
name: attr.id,
|
||||||
oncreate: focus
|
oncreate: focus
|
||||||
? _vnode => {
|
? _vnode => {
|
||||||
_vnode.dom.focus();
|
(_vnode.dom as HTMLInputElement).focus();
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
onchange: e => {
|
onchange: e => {
|
||||||
@@ -83,7 +86,7 @@ function createField(current, attr, focus) {
|
|||||||
value: current.object[attr.id],
|
value: current.object[attr.id],
|
||||||
oncreate: focus
|
oncreate: focus
|
||||||
? _vnode => {
|
? _vnode => {
|
||||||
_vnode.dom.focus();
|
(_vnode.dom as HTMLInputElement).focus();
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
oninput: e => {
|
oninput: e => {
|
||||||
@@ -93,21 +96,21 @@ function createField(current, attr, focus) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function component() {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
const actionHandler = vnode.attrs.actionHandler;
|
const actionHandler = vnode.attrs["actionHandler"];
|
||||||
const attributes = vnode.attrs.attributes;
|
const attributes = vnode.attrs["attributes"];
|
||||||
const resource = vnode.attrs.resource;
|
const resource = vnode.attrs["resource"];
|
||||||
const base = vnode.attrs.base || {};
|
const base = vnode.attrs["base"] || {};
|
||||||
if (!vnode.state.current) {
|
if (!vnode.state["current"]) {
|
||||||
vnode.state.current = {
|
vnode.state["current"] = {
|
||||||
isNew: !base["_id"],
|
isNew: !base["_id"],
|
||||||
object: Object.assign({}, base)
|
object: Object.assign({}, base)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = vnode.state.current;
|
const current = vnode.state["current"];
|
||||||
|
|
||||||
const form = [];
|
const form = [];
|
||||||
let focused = false;
|
let focused = false;
|
||||||
@@ -126,27 +129,29 @@ export default function component() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = m("button.primary", { type: "submit" }, "Save");
|
const submit = m(
|
||||||
|
"button.primary",
|
||||||
|
{ type: "submit" },
|
||||||
|
"Save"
|
||||||
|
) as VnodeDOM;
|
||||||
const buttons = [submit];
|
const buttons = [submit];
|
||||||
|
|
||||||
if (!current.isNew) {
|
if (!current.isNew) {
|
||||||
buttons.push(
|
buttons.push(m(
|
||||||
m(
|
"button.primary",
|
||||||
"button.primary",
|
{
|
||||||
{
|
type: "button",
|
||||||
type: "button",
|
title: `Delete ${singular[resource] || resource}`,
|
||||||
title: `Delete ${singular[resource] || resource}`,
|
onclick: e => {
|
||||||
onclick: e => {
|
e.redraw = false;
|
||||||
e.redraw = false;
|
e.target.disabled = true;
|
||||||
e.target.disabled = true;
|
actionHandler("delete", current.object).then(() => {
|
||||||
actionHandler("delete", current.object).then(() => {
|
e.target.disabled = false;
|
||||||
e.target.disabled = false;
|
});
|
||||||
});
|
}
|
||||||
}
|
},
|
||||||
},
|
"Delete"
|
||||||
"Delete"
|
) as VnodeDOM);
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form.push(m(".actions-bar", buttons));
|
form.push(m(".actions-bar", buttons));
|
||||||
@@ -165,12 +170,12 @@ export default function component() {
|
|||||||
// const onsubmit = e.target.onsubmit;
|
// const onsubmit = e.target.onsubmit;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// e.target.onsubmit = null;
|
// e.target.onsubmit = null;
|
||||||
submit.dom.disabled = true;
|
(submit.dom as HTMLFormElement).disabled = true;
|
||||||
// submit.dom.textContent = "Loading ...";
|
// submit.dom.textContent = "Loading ...";
|
||||||
actionHandler("save", current.object).then(() => {
|
actionHandler("save", current.object).then(() => {
|
||||||
// submit.dom.textContent = "Save";
|
// submit.dom.textContent = "Save";
|
||||||
// e.target.onsubmit = onsubmit;
|
// e.target.onsubmit = onsubmit;
|
||||||
submit.dom.disabled = false;
|
(submit.dom as HTMLFormElement).disabled = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -181,4 +186,6 @@ export default function component() {
|
|||||||
return m("div.put-form", children);
|
return m("div.put-form", children);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
|
import { Expression } from "../lib/types";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
devices: {},
|
devices: {},
|
||||||
@@ -34,19 +33,23 @@ const resources = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const v of Object.values(config.ui.filters)) {
|
for (const v of Object.values(config.ui.filters as {
|
||||||
|
label: string;
|
||||||
|
parameter: string;
|
||||||
|
type: string;
|
||||||
|
}[])) {
|
||||||
resources.devices[v.label] = {
|
resources.devices[v.label] = {
|
||||||
parameter: v.parameter,
|
parameter: v.parameter,
|
||||||
type: (v.type || "").split(",").map(s => s.trim())
|
type: (v.type || "").split(",").map(s => s.trim())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLabels(resource) {
|
export function getLabels(resource): string[] {
|
||||||
if (!resources[resource]) return [];
|
if (!resources[resource]) return [];
|
||||||
return Object.keys(resources[resource]);
|
return Object.keys(resources[resource]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryNumber(param, value) {
|
function queryNumber(param, value): Expression {
|
||||||
let op = "=";
|
let op = "=";
|
||||||
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
||||||
if (value.startsWith(o)) {
|
if (value.startsWith(o)) {
|
||||||
@@ -62,7 +65,7 @@ function queryNumber(param, value) {
|
|||||||
return [op, param, v];
|
return [op, param, v];
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryTimestamp(param, value) {
|
function queryTimestamp(param, value): Expression {
|
||||||
let op = "=";
|
let op = "=";
|
||||||
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
for (const o of ["<>", "=", "<=", "<", ">=", ">"]) {
|
||||||
if (value.startsWith(o)) {
|
if (value.startsWith(o)) {
|
||||||
@@ -78,11 +81,11 @@ function queryTimestamp(param, value) {
|
|||||||
return [op, param, v];
|
return [op, param, v];
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryString(param, value) {
|
function queryString(param, value): Expression {
|
||||||
return ["LIKE", ["FUNC", "LOWER", param], value.toLowerCase()];
|
return ["LIKE", ["FUNC", "LOWER", param], value.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryMac(param, value) {
|
function queryMac(param, value): Expression {
|
||||||
value = value.replace(/[^a-f0-9]/gi, "").toLowerCase();
|
value = value.replace(/[^a-f0-9]/gi, "").toLowerCase();
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
if (value.length === 12) {
|
if (value.length === 12) {
|
||||||
@@ -104,11 +107,11 @@ function queryMac(param, value) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function unpack(resource, label, value) {
|
export function unpack(resource, label, value): Expression {
|
||||||
if (!resources[resource]) return null;
|
if (!resources[resource]) return null;
|
||||||
const type = resources[resource][label].type;
|
const type = resources[resource][label].type;
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
const res = ["OR"];
|
const res: Expression = ["OR"];
|
||||||
|
|
||||||
if (type.length === 0 || type.includes("number")) {
|
if (type.length === 0 || type.includes("number")) {
|
||||||
const q = queryNumber(resources[resource][label].parameter, value);
|
const q = queryNumber(resources[resource][label].parameter, value);
|
||||||
@@ -134,5 +137,3 @@ function unpack(resource, label, value) {
|
|||||||
else if (res.length === 2) return res[1];
|
else if (res.length === 2) return res[1];
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getLabels, unpack };
|
|
||||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user