Compare commits
5
Commits
v1.2.1
..
attributes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6733cef5dd | ||
|
|
96add7efe2 | ||
|
|
d311dad861 | ||
|
|
4a55f56e48 | ||
|
|
601690e3b5 |
No files matched your search
@@ -21,7 +21,6 @@
|
|||||||
"@typescript-eslint/no-explicit-any": 0,
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
"@typescript-eslint/no-use-before-define": ["error", { "functions": false }],
|
"@typescript-eslint/no-use-before-define": ["error", { "functions": false }],
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }],
|
"@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }],
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
|
||||||
"require-atomic-updates": 0,
|
"require-atomic-updates": 0,
|
||||||
"no-prototype-builtins": 0
|
"no-prototype-builtins": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,5 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
## 1.2.1 (2020-09-08)
|
|
||||||
- Fix bug causing faults to not be displayed in the UI.
|
|
||||||
- Fix bug where deleting objects does not get reflected immediately in the UI.
|
|
||||||
- Improve conversion between filters written in the expression format and
|
|
||||||
MongoDB queries. There should now be fewer edge cases where the two are not
|
|
||||||
equisatisfiable.
|
|
||||||
|
|
||||||
## 1.2.0 (2020-09-01)
|
|
||||||
|
|
||||||
- Support GetParameterAttributes and SetParameterAttributes TR-069 methods.
|
|
||||||
- Support CASE statement and COALESCE function in expressions.
|
|
||||||
- Provision arguments can now be a list of expressions that are dynamically
|
|
||||||
evaluated.
|
|
||||||
- Support Forwarded HTTP header to display in the logs the correct IP of CPEs
|
|
||||||
behind a reverse proxy. Must be configured using FORWARDED_HEADER option.
|
|
||||||
- Config expressions can now access all available device parameters, not only
|
|
||||||
serial number, product class, and OUI.
|
|
||||||
- Use relative URLs throughout the UI to allow serving from a subdirectory
|
|
||||||
using a reverse proxy.
|
|
||||||
- Make Date.parse() and Date.UTC() available to provision scripts.
|
|
||||||
- libxmljs has been entirely removed in favor of our bespoke XML parser.
|
|
||||||
- Removed the config option CWMP_KEEP_ALIVE_TIMEOUT. SESSION_TIMEOUT is now
|
|
||||||
used to determine the TCP connection timeout.
|
|
||||||
- The all-parameters component now limits the number of parameters displayed
|
|
||||||
for better performance.
|
|
||||||
- The process genieacs-cwmp is now much less likely to throw exceptions as a
|
|
||||||
result of invalid requests from CPE.
|
|
||||||
- A large number of bug fixes and stability improvements.
|
|
||||||
|
|
||||||
## 1.2.0-beta.0 (2019-07-30)
|
## 1.2.0-beta.0 (2019-07-30)
|
||||||
|
|
||||||
- A brand new UI superseding genieacs-gui.
|
- A brand new UI superseding genieacs-gui.
|
||||||
|
|||||||
+8
-7
@@ -1,21 +1,22 @@
|
|||||||
# Have a question?
|
# Have a question?
|
||||||
|
|
||||||
Please ask in the [forum] instead of opening a Github Issue. The Github issue
|
Please ask in the [Users mailing list][mailing-lists] instead of opening a
|
||||||
tracker is strictly for bug reports.
|
Github Issue. The Github issue tracker is strictly for bug reports.
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
We use GitHub issues to track bugs. Please ensure your bug description is
|
We use GitHub issues to track bugs. Please ensure your bug description is
|
||||||
clear and has sufficient instructions to be able to reproduce the issue.
|
clear and has sufficient instructions to be able to reproduce the issue.
|
||||||
|
|
||||||
If you face interoperability issues with your CPE, more often than not it's a
|
If you face interoperability issues with your device, more often than not it's
|
||||||
device related issue. Please consult the [forum] before submitting an Issue.
|
a device related issue. Please consult the [Users mailing list][mailing-lists]
|
||||||
|
before submitting an Issue.
|
||||||
|
|
||||||
# Pull Requests
|
# Pull Requests
|
||||||
|
|
||||||
Pull requests are welcome. If you have a fix for a bug, go ahead and create a
|
Pull requests are welcome. If you have a fix for a bug, go ahead and create a
|
||||||
pull request. For other kinds of enhancements, please discuss in the [forum]
|
pull request. For other kinds of enhancements, please discuss in the [Users
|
||||||
first.
|
mailing list][mailing-lists] first.
|
||||||
|
|
||||||
Please follow the guidelines [here][commit-guidelines] when committing your
|
Please follow the guidelines [here][commit-guidelines] when committing your
|
||||||
changes.
|
changes.
|
||||||
@@ -35,4 +36,4 @@ source code contained therein, and that your content does not infringe upon
|
|||||||
the intellectual property rights of a third party.
|
the intellectual property rights of a third party.
|
||||||
|
|
||||||
[commit-guidelines]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
[commit-guidelines]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||||
[forum]: https://forum.genieacs.com
|
[mailing-lists]: http://lists.genieacs.com
|
||||||
@@ -8,31 +8,36 @@ millions of concurrent devices.
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
*Important: This is a pre-release branch. Use
|
||||||
|
[v1.1](https://github.com/genieacs/genieacs/tree/v1.1) for production
|
||||||
|
deployments.*
|
||||||
|
|
||||||
Install [Node.js](http://nodejs.org/) and [MongoDB](http://www.mongodb.org/).
|
Install [Node.js](http://nodejs.org/) and [MongoDB](http://www.mongodb.org/).
|
||||||
Refer to their corresponding documentation for installation instructions. The
|
Refer to their corresponding documentation for installation instructions. The
|
||||||
supported versions are:
|
supported versions are:
|
||||||
|
|
||||||
- Node.js: 12.3+
|
- Node.js: 10.x through 12.x
|
||||||
- MongoDB: 3.6+
|
- MongoDB: 2.6 through 4.1
|
||||||
|
|
||||||
Install GenieACS from NPM:
|
Clone this repo or download the source archive then *cd* into the source
|
||||||
|
directory and install the required dependencies:
|
||||||
sudo npm install -g --unsafe-perm genieacs
|
|
||||||
|
|
||||||
To build from source instead, clone this repo or download the source archive
|
|
||||||
then *cd* into the source directory then run:
|
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
Now build:
|
||||||
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
Finally, run the following services (found under `./dist/bin/` if building from
|
Finally, run the following services from the build that's generated under
|
||||||
source):
|
'dist' directory:
|
||||||
|
|
||||||
### genieacs-cwmp
|
### genieacs-cwmp
|
||||||
|
|
||||||
This is the service that the CPEs will communicate with. It listens on port
|
This is the service that the CPEs will communicate with. It listens on port
|
||||||
7547 by default. Configure the ACS URL in your devices accordingly.
|
7547 by default. Configure the ACS URL in your devices accordingly.
|
||||||
|
|
||||||
|
./dist/bin/genieacs-cwmp
|
||||||
|
|
||||||
You may optionally use [genieacs-sim](https://github.com/genieacs/genieacs-sim)
|
You may optionally use [genieacs-sim](https://github.com/genieacs/genieacs-sim)
|
||||||
as a dummy TR-069 simulator if you don't have a CPE at hand.
|
as a dummy TR-069 simulator if you don't have a CPE at hand.
|
||||||
|
|
||||||
@@ -42,36 +47,40 @@ This is the northbound interface module. It exposes a REST API on port 7557 by
|
|||||||
default. This one is only required if you have an external system integrating
|
default. This one is only required if you have an external system integrating
|
||||||
with GenieACS using this API.
|
with GenieACS using this API.
|
||||||
|
|
||||||
|
./dist/bin/genieacs-nbi
|
||||||
|
|
||||||
### genieacs-fs
|
### genieacs-fs
|
||||||
|
|
||||||
This is the file server from which the CPEs will download firmware images and
|
This is the file server from which the CPEs will download firmware images and
|
||||||
such. It listens on port 7567 by default.
|
such.
|
||||||
|
|
||||||
|
./dist/bin/genieacs-fs
|
||||||
|
|
||||||
### genieacs-ui
|
### genieacs-ui
|
||||||
|
|
||||||
This serves the web based user interface. It listens on port 3000 by default.
|
This serves the web based user interface. It listens on port 3000 by default.
|
||||||
You must pass *--ui-jwt-secret* argument to supply the secret key used for
|
|
||||||
signing browser cookies:
|
|
||||||
|
|
||||||
genieacs-ui --ui-jwt-secret secret
|
./dist/bin/genieacs-ui --ui-jwt-secret secret
|
||||||
|
|
||||||
|
The argument *--ui-jwt-secret* supplies the key used for signing browser
|
||||||
|
cookies.
|
||||||
|
|
||||||
The UI has plenty of configuration options. When you open GenieACS's UI in a
|
The UI has plenty of configuration options. When you open GenieACS's UI in a
|
||||||
browser you'll be greeted with a database initialization wizard to help you
|
browser you'll be greeted with a database initialization wizard to help you
|
||||||
populate some initial configuration.
|
populate some initial configuration.
|
||||||
|
|
||||||
Visit [docs.genieacs.com](https://docs.genieacs.com) for more documentation and
|
|
||||||
a complete installation guide for production deployments.
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
The [forum](https://forum.genieacs.com) is a good place to get guidance and
|
The [forum](https://forum.genieacs.com) is a good place to get guidance and
|
||||||
help from the community. Head on over and join the conversation!
|
help from the community. Head on over and join the conversation! In addition,
|
||||||
|
the [wiki](https://github.com/genieacs/genieacs/wiki) provides useful
|
||||||
|
documentation and tips from GenieACS users.
|
||||||
|
|
||||||
For commercial support options, please visit
|
For commercial support options and professional services, please visit
|
||||||
[genieacs.com](https://genieacs.com/support/).
|
[genieacs.com](https://genieacs.com/support/).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright 2013-2020 GenieACS Inc. GenieACS is released under the [AGPLv3
|
Copyright 2013-2019 GenieACS Inc. GenieACS is released under the [AGPLv3
|
||||||
license
|
license
|
||||||
terms](https://raw.githubusercontent.com/genieacs/genieacs/master/LICENSE).
|
terms](https://raw.githubusercontent.com/genieacs/genieacs/master/LICENSE).
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
|
"compileEnhancements": false,
|
||||||
"extensions": ["ts"],
|
"extensions": ["ts"],
|
||||||
"require": ["esm", "ts-node/register/transpile-only"]
|
"require": ["esm", "ts-node/register/transpile-only"]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -29,8 +31,8 @@ import { version as VERSION } from "../package.json";
|
|||||||
|
|
||||||
logger.init("cwmp", VERSION);
|
logger.init("cwmp", VERSION);
|
||||||
|
|
||||||
const SERVICE_ADDRESS = config.get("CWMP_INTERFACE") as string;
|
const SERVICE_ADDRESS = config.get("CWMP_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("CWMP_PORT") as number;
|
const SERVICE_PORT = config.get("CWMP_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully(): void {
|
function exitWorkerGracefully(): void {
|
||||||
setTimeout(exitWorkerUngracefully, 5000).unref();
|
setTimeout(exitWorkerUngracefully, 5000).unref();
|
||||||
@@ -38,23 +40,23 @@ function exitWorkerGracefully(): void {
|
|||||||
db.disconnect(),
|
db.disconnect(),
|
||||||
cache.disconnect(),
|
cache.disconnect(),
|
||||||
extensions.killAll(),
|
extensions.killAll(),
|
||||||
cluster.worker.disconnect(),
|
cluster.worker.disconnect()
|
||||||
]).catch(exitWorkerUngracefully);
|
]).catch(exitWorkerUngracefully);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully(): void {
|
function exitWorkerUngracefully(): void {
|
||||||
extensions.killAll().finally(() => {
|
extensions.killAll().then(() => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.worker) {
|
if (!cluster.worker) {
|
||||||
const WORKER_COUNT = config.get("CWMP_WORKER_PROCESSES") as number;
|
const WORKER_COUNT = config.get("CWMP_WORKER_PROCESSES");
|
||||||
|
|
||||||
logger.info({
|
logger.info({
|
||||||
message: `genieacs-cwmp starting`,
|
message: `genieacs-cwmp starting`,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
version: VERSION,
|
version: VERSION
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
||||||
@@ -62,7 +64,7 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGINT, exiting",
|
message: "Received signal SIGINT, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
@@ -71,25 +73,28 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGTERM, exiting",
|
message: "Received signal SIGTERM, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const ssl = {
|
const ssl = {
|
||||||
key: config.get("CWMP_SSL_KEY") as string,
|
key: config.get("CWMP_SSL_KEY"),
|
||||||
cert: config.get("CWMP_SSL_CERT") as string,
|
cert: config.get("CWMP_SSL_CERT")
|
||||||
};
|
};
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
process.on("uncaughtException", err => {
|
||||||
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
||||||
logger.error({
|
logger.error({
|
||||||
message: "Uncaught exception",
|
message: "Uncaught exception",
|
||||||
exception: err,
|
exception: err,
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
|
|
||||||
const initPromise = Promise.all([db.connect(), cache.connect()])
|
const initPromise = Promise.all([db.connect(), cache.connect()])
|
||||||
@@ -103,7 +108,7 @@ if (!cluster.worker) {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
@@ -111,13 +116,19 @@ if (!cluster.worker) {
|
|||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -33,8 +35,8 @@ function errorToFault(err: Error): Fault {
|
|||||||
message: err.message,
|
message: err.message,
|
||||||
detail: {
|
detail: {
|
||||||
name: err.name,
|
name: err.name,
|
||||||
message: err.message,
|
message: err.message
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (err.stack) {
|
if (err.stack) {
|
||||||
@@ -54,16 +56,16 @@ function errorToFault(err: Error): Fault {
|
|||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
process.on("uncaughtException", err => {
|
||||||
const fault = errorToFault(err);
|
const fault = errorToFault(err);
|
||||||
jobs.forEach((jobId) => {
|
jobs.forEach(jobId => {
|
||||||
process.send([jobId, fault, null]);
|
process.send([jobId, fault, null]);
|
||||||
});
|
});
|
||||||
jobs.clear();
|
jobs.clear();
|
||||||
process.disconnect();
|
process.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("message", (message) => {
|
process.on("message", message => {
|
||||||
jobs.add(message[0]);
|
jobs.add(message[0]);
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
@@ -77,7 +79,7 @@ process.on("message", (message) => {
|
|||||||
if (!script[funcName]) {
|
if (!script[funcName]) {
|
||||||
const fault = {
|
const fault = {
|
||||||
code: "ext",
|
code: "ext",
|
||||||
message: `No such function '${funcName}' in extension '${fileName}'`,
|
message: `No such function '${funcName}' in extension '${fileName}'`
|
||||||
};
|
};
|
||||||
process.send([message[0], fault, null]);
|
process.send([message[0], fault, null]);
|
||||||
return;
|
return;
|
||||||
@@ -91,6 +93,4 @@ process.on("message", (message) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Ignore SIGINT
|
// Ignore SIGINT
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {});
|
||||||
// Ignore
|
|
||||||
});
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -28,15 +30,15 @@ import { version as VERSION } from "../package.json";
|
|||||||
|
|
||||||
logger.init("fs", VERSION);
|
logger.init("fs", VERSION);
|
||||||
|
|
||||||
const SERVICE_ADDRESS = config.get("FS_INTERFACE") as string;
|
const SERVICE_ADDRESS = config.get("FS_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("FS_PORT") as number;
|
const SERVICE_PORT = config.get("FS_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully(): void {
|
function exitWorkerGracefully(): void {
|
||||||
setTimeout(exitWorkerUngracefully, 5000).unref();
|
setTimeout(exitWorkerUngracefully, 5000).unref();
|
||||||
Promise.all([
|
Promise.all([
|
||||||
db.disconnect(),
|
db.disconnect(),
|
||||||
cache.disconnect(),
|
cache.disconnect(),
|
||||||
cluster.worker.disconnect(),
|
cluster.worker.disconnect()
|
||||||
]).catch(exitWorkerUngracefully);
|
]).catch(exitWorkerUngracefully);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,12 +47,12 @@ function exitWorkerUngracefully(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.worker) {
|
if (!cluster.worker) {
|
||||||
const WORKER_COUNT = config.get("FS_WORKER_PROCESSES") as number;
|
const WORKER_COUNT = config.get("FS_WORKER_PROCESSES");
|
||||||
|
|
||||||
logger.info({
|
logger.info({
|
||||||
message: `genieacs-fs starting`,
|
message: `genieacs-fs starting`,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
version: VERSION,
|
version: VERSION
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
||||||
@@ -58,7 +60,7 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGINT, exiting",
|
message: "Received signal SIGINT, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
@@ -67,56 +69,55 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGTERM, exiting",
|
message: "Received signal SIGTERM, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const ssl = {
|
const ssl = {
|
||||||
key: config.get("FS_SSL_KEY") as string,
|
key: config.get("FS_SSL_KEY"),
|
||||||
cert: config.get("FS_SSL_CERT") as string,
|
cert: config.get("FS_SSL_CERT")
|
||||||
};
|
};
|
||||||
|
|
||||||
let stopping = false;
|
process.on("uncaughtException", err => {
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
|
||||||
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
||||||
logger.error({
|
logger.error({
|
||||||
message: "Uncaught exception",
|
message: "Uncaught exception",
|
||||||
exception: err,
|
exception: err,
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
stopping = true;
|
server
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
|
|
||||||
const _listener = (req, res): void => {
|
|
||||||
if (stopping) res.setHeader("Connection", "close");
|
|
||||||
listener(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initPromise = Promise.all([db.connect(), cache.connect()])
|
const initPromise = Promise.all([db.connect(), cache.connect()])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
|
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, listener);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
stopping = true;
|
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
stopping = true;
|
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -29,8 +31,8 @@ import { version as VERSION } from "../package.json";
|
|||||||
|
|
||||||
logger.init("nbi", VERSION);
|
logger.init("nbi", VERSION);
|
||||||
|
|
||||||
const SERVICE_ADDRESS = config.get("NBI_INTERFACE") as string;
|
const SERVICE_ADDRESS = config.get("NBI_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("NBI_PORT") as number;
|
const SERVICE_PORT = config.get("NBI_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully(): void {
|
function exitWorkerGracefully(): void {
|
||||||
setTimeout(exitWorkerUngracefully, 5000).unref();
|
setTimeout(exitWorkerUngracefully, 5000).unref();
|
||||||
@@ -38,23 +40,23 @@ function exitWorkerGracefully(): void {
|
|||||||
db.disconnect(),
|
db.disconnect(),
|
||||||
cache.disconnect(),
|
cache.disconnect(),
|
||||||
extensions.killAll(),
|
extensions.killAll(),
|
||||||
cluster.worker.disconnect(),
|
cluster.worker.disconnect()
|
||||||
]).catch(exitWorkerUngracefully);
|
]).catch(exitWorkerUngracefully);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully(): void {
|
function exitWorkerUngracefully(): void {
|
||||||
extensions.killAll().finally(() => {
|
extensions.killAll().then(() => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.worker) {
|
if (!cluster.worker) {
|
||||||
const WORKER_COUNT = config.get("NBI_WORKER_PROCESSES") as number;
|
const WORKER_COUNT = config.get("NBI_WORKER_PROCESSES");
|
||||||
|
|
||||||
logger.info({
|
logger.info({
|
||||||
message: `genieacs-nbi starting`,
|
message: `genieacs-nbi starting`,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
version: VERSION,
|
version: VERSION
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
||||||
@@ -62,7 +64,7 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGINT, exiting",
|
message: "Received signal SIGINT, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
@@ -71,56 +73,55 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGTERM, exiting",
|
message: "Received signal SIGTERM, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const ssl = {
|
const ssl = {
|
||||||
key: config.get("NBI_SSL_KEY") as string,
|
key: config.get("NBI_SSL_KEY"),
|
||||||
cert: config.get("NBI_SSL_CERT") as string,
|
cert: config.get("NBI_SSL_CERT")
|
||||||
};
|
};
|
||||||
|
|
||||||
let stopping = false;
|
process.on("uncaughtException", err => {
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
|
||||||
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
||||||
logger.error({
|
logger.error({
|
||||||
message: "Uncaught exception",
|
message: "Uncaught exception",
|
||||||
exception: err,
|
exception: err,
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
stopping = true;
|
server
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
|
|
||||||
const _listener = (req, res): void => {
|
|
||||||
if (stopping) res.setHeader("Connection", "close");
|
|
||||||
listener(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initPromise = Promise.all([db.connect(), cache.connect()])
|
const initPromise = Promise.all([db.connect(), cache.connect()])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
|
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, listener);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
stopping = true;
|
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
stopping = true;
|
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -30,8 +32,8 @@ import { version as VERSION } from "../package.json";
|
|||||||
|
|
||||||
logger.init("ui", VERSION);
|
logger.init("ui", VERSION);
|
||||||
|
|
||||||
const SERVICE_ADDRESS = config.get("UI_INTERFACE") as string;
|
const SERVICE_ADDRESS = config.get("UI_INTERFACE");
|
||||||
const SERVICE_PORT = config.get("UI_PORT") as number;
|
const SERVICE_PORT = config.get("UI_PORT");
|
||||||
|
|
||||||
function exitWorkerGracefully(): void {
|
function exitWorkerGracefully(): void {
|
||||||
setTimeout(exitWorkerUngracefully, 5000).unref();
|
setTimeout(exitWorkerUngracefully, 5000).unref();
|
||||||
@@ -40,23 +42,23 @@ function exitWorkerGracefully(): void {
|
|||||||
db2.disconnect(),
|
db2.disconnect(),
|
||||||
cache.disconnect(),
|
cache.disconnect(),
|
||||||
extensions.killAll(),
|
extensions.killAll(),
|
||||||
cluster.worker.disconnect(),
|
cluster.worker.disconnect()
|
||||||
]).catch(exitWorkerUngracefully);
|
]).catch(exitWorkerUngracefully);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitWorkerUngracefully(): void {
|
function exitWorkerUngracefully(): void {
|
||||||
extensions.killAll().finally(() => {
|
extensions.killAll().then(() => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.worker) {
|
if (!cluster.worker) {
|
||||||
const WORKER_COUNT = config.get("UI_WORKER_PROCESSES") as number;
|
const WORKER_COUNT = config.get("UI_WORKER_PROCESSES");
|
||||||
|
|
||||||
logger.info({
|
logger.info({
|
||||||
message: `genieacs-ui starting`,
|
message: `genieacs-ui starting`,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
version: VERSION,
|
version: VERSION
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
cluster.start(WORKER_COUNT, SERVICE_PORT, SERVICE_ADDRESS);
|
||||||
@@ -64,7 +66,7 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGINT, exiting",
|
message: "Received signal SIGINT, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
@@ -73,42 +75,43 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
logger.info({
|
logger.info({
|
||||||
message: "Received signal SIGTERM, exiting",
|
message: "Received signal SIGTERM, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.stop();
|
cluster.stop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const ssl = {
|
const ssl = {
|
||||||
key: config.get("UI_SSL_KEY") as string,
|
key: config.get("UI_SSL_KEY"),
|
||||||
cert: config.get("UI_SSL_CERT") as string,
|
cert: config.get("UI_SSL_CERT")
|
||||||
};
|
};
|
||||||
|
|
||||||
let stopping = false;
|
let stopping = false;
|
||||||
|
|
||||||
process.on("uncaughtException", (err) => {
|
process.on("uncaughtException", err => {
|
||||||
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
if ((err as NodeJS.ErrnoException).code === "ERR_IPC_DISCONNECTED") return;
|
||||||
logger.error({
|
logger.error({
|
||||||
message: "Uncaught exception",
|
message: "Uncaught exception",
|
||||||
exception: err,
|
exception: err,
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
stopping = true;
|
stopping = true;
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
|
|
||||||
const _listener = (req, res): void => {
|
const _listener = (req, res): void => {
|
||||||
if (stopping) res.setHeader("Connection", "close");
|
if (stopping) res.setHeader("Connection", "close");
|
||||||
listener(req, res).catch((err) => {
|
listener(req, res);
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const initPromise = Promise.all([db2.connect(), cache.connect()])
|
const initPromise = Promise.all([db2.connect(), cache.connect()])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
|
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
@@ -117,14 +120,20 @@ if (!cluster.worker) {
|
|||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
stopping = true;
|
stopping = true;
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
stopping = true;
|
stopping = true;
|
||||||
initPromise.finally(() => {
|
initPromise.finally(() => {
|
||||||
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
|
server
|
||||||
|
.stop()
|
||||||
|
.then(exitWorkerGracefully)
|
||||||
|
.catch(exitWorkerUngracefully);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
+52
-65
@@ -21,11 +21,10 @@ import * as path from "path";
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { rollup, WarningHandler } from "rollup";
|
import { rollup, WarningHandler } from "rollup";
|
||||||
import rollupJson from "@rollup/plugin-json";
|
import rollupReplace from "rollup-plugin-replace";
|
||||||
import typescript from "@rollup/plugin-typescript";
|
import rollupJson from "rollup-plugin-json";
|
||||||
|
import typescript from "rollup-plugin-typescript";
|
||||||
import { terser } from "rollup-plugin-terser";
|
import { terser } from "rollup-plugin-terser";
|
||||||
import nodeResolve from "@rollup/plugin-node-resolve";
|
|
||||||
import commonjs from "@rollup/plugin-commonjs";
|
|
||||||
import webpack from "webpack";
|
import webpack from "webpack";
|
||||||
import postcss from "postcss";
|
import postcss from "postcss";
|
||||||
import postcssImport from "postcss-import";
|
import postcssImport from "postcss-import";
|
||||||
@@ -56,6 +55,7 @@ const externals = [
|
|||||||
"crypto",
|
"crypto",
|
||||||
"util",
|
"util",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
|
"libxmljs",
|
||||||
"vm",
|
"vm",
|
||||||
"later",
|
"later",
|
||||||
"parsimmon",
|
"parsimmon",
|
||||||
@@ -78,10 +78,7 @@ const externals = [
|
|||||||
"yaml",
|
"yaml",
|
||||||
"codemirror",
|
"codemirror",
|
||||||
"codemirror/mode/javascript/javascript",
|
"codemirror/mode/javascript/javascript",
|
||||||
"codemirror/mode/yaml/yaml",
|
"codemirror/mode/yaml/yaml"
|
||||||
"ipaddr.js",
|
|
||||||
"jsbi",
|
|
||||||
"espresso-iisojs",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function rmDirSync(dirPath): void {
|
function rmDirSync(dirPath): void {
|
||||||
@@ -126,7 +123,9 @@ function generateSymbol(id: string, svgStr: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const symbolBody = xml.children[0].children
|
const symbolBody = xml.children[0].children
|
||||||
.map((c) => xmlTostring(c))
|
.map(c => {
|
||||||
|
return xmlTostring(c);
|
||||||
|
})
|
||||||
.join("");
|
.join("");
|
||||||
return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`;
|
return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`;
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ async function init(): Promise<void> {
|
|||||||
delete packageJson["devDependencies"];
|
delete packageJson["devDependencies"];
|
||||||
packageJson["scripts"] = {
|
packageJson["scripts"] = {
|
||||||
install: packageJson["scripts"].install,
|
install: packageJson["scripts"].install,
|
||||||
configure: packageJson["scripts"].configure,
|
configure: packageJson["scripts"].configure
|
||||||
};
|
};
|
||||||
packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`;
|
packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`;
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
@@ -174,7 +173,7 @@ async function copyStatic(): Promise<void> {
|
|||||||
"README.md",
|
"README.md",
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"public/logo.svg",
|
"public/logo.svg",
|
||||||
"public/favicon.png",
|
"public/favicon.png"
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
@@ -195,31 +194,35 @@ async function generateCss(): Promise<void> {
|
|||||||
stage: 3,
|
stage: 3,
|
||||||
features: {
|
features: {
|
||||||
"nesting-rules": true,
|
"nesting-rules": true,
|
||||||
"color-mod-function": true,
|
"color-mod-function": true
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
cssnano,
|
cssnano
|
||||||
]).process(cssIn, { from: cssInPath, to: cssOutPath });
|
]).process(cssIn, { from: cssInPath, to: cssOutPath });
|
||||||
fs.writeFileSync(cssOutPath, cssOut.css);
|
fs.writeFileSync(cssOutPath, cssOut.css);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateToolsJs(): Promise<void> {
|
async function generateToolsJs(): Promise<void> {
|
||||||
for (const bin of ["dump-data-model"]) {
|
for (const bin of ["dump-data-model"]) {
|
||||||
const inputFile = path.resolve(INPUT_DIR, `tools/${bin}.ts`);
|
const inputFile = path.resolve(INPUT_DIR, `tools/${bin}`);
|
||||||
const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`);
|
const outputFile = path.resolve(OUTPUT_DIR, `tools/${bin}`);
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: inputFile,
|
input: inputFile,
|
||||||
external: externals,
|
external: externals,
|
||||||
acorn: {
|
acorn: {
|
||||||
allowHashBang: true,
|
allowHashBang: true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
rollupReplace({
|
||||||
|
delimiters: ["", ""],
|
||||||
|
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
|
||||||
|
}),
|
||||||
typescript({
|
typescript({
|
||||||
tsconfig: "./tsconfig.json",
|
tsconfig: "./tsconfig.json",
|
||||||
include: [`tools/${bin}.ts`, "lib/**/*.ts"],
|
include: [`tools/${bin}`, "lib/**/*.ts"]
|
||||||
}),
|
}),
|
||||||
MODE === "production" ? terser() : null,
|
MODE === "production" ? terser() : null
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.write({
|
||||||
@@ -228,7 +231,7 @@ async function generateToolsJs(): Promise<void> {
|
|||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
sourcemapExcludeSources: true,
|
sourcemapExcludeSources: true,
|
||||||
banner: "#!/usr/bin/env node",
|
banner: "#!/usr/bin/env node",
|
||||||
file: outputFile,
|
file: outputFile
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as executable
|
// Mark as executable
|
||||||
@@ -243,24 +246,27 @@ async function generateBackendJs(): Promise<void> {
|
|||||||
"genieacs-ext",
|
"genieacs-ext",
|
||||||
"genieacs-nbi",
|
"genieacs-nbi",
|
||||||
"genieacs-fs",
|
"genieacs-fs",
|
||||||
"genieacs-ui",
|
"genieacs-ui"
|
||||||
]) {
|
]) {
|
||||||
const inputFile = path.resolve(INPUT_DIR, `bin/${bin}.ts`);
|
const inputFile = path.resolve(INPUT_DIR, `bin/${bin}`);
|
||||||
const outputFile = path.resolve(OUTPUT_DIR, `bin/${bin}`);
|
const outputFile = path.resolve(OUTPUT_DIR, `bin/${bin}`);
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: inputFile,
|
input: inputFile,
|
||||||
external: externals,
|
external: externals,
|
||||||
acorn: {
|
acorn: {
|
||||||
allowHashBang: true,
|
allowHashBang: true
|
||||||
},
|
},
|
||||||
treeshake: {
|
treeshake: {
|
||||||
propertyReadSideEffects: false,
|
propertyReadSideEffects: false,
|
||||||
moduleSideEffects: false,
|
pureExternalModules: true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
rollupReplace({
|
||||||
|
delimiters: ["", ""],
|
||||||
|
"#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only": ""
|
||||||
|
}),
|
||||||
rollupJson({ preferConst: true }),
|
rollupJson({ preferConst: true }),
|
||||||
{
|
{
|
||||||
name: "",
|
|
||||||
resolveId: (importee, importer) => {
|
resolveId: (importee, importer) => {
|
||||||
if (importee.endsWith("/package.json")) {
|
if (importee.endsWith("/package.json")) {
|
||||||
const p = path.resolve(path.dirname(importer), importee);
|
const p = path.resolve(path.dirname(importer), importee);
|
||||||
@@ -268,14 +274,14 @@ async function generateBackendJs(): Promise<void> {
|
|||||||
return path.resolve(OUTPUT_DIR, "package.json");
|
return path.resolve(OUTPUT_DIR, "package.json");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
typescript({
|
typescript({
|
||||||
tsconfig: "./tsconfig.json",
|
tsconfig: "./tsconfig.json",
|
||||||
include: [`bin/${bin}.ts`, "lib/**/*.ts"],
|
include: [`bin/${bin}`, "lib/**/*.ts"]
|
||||||
}),
|
}),
|
||||||
MODE === "production" ? terser() : null,
|
MODE === "production" ? terser() : null
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.write({
|
||||||
@@ -284,7 +290,7 @@ async function generateBackendJs(): Promise<void> {
|
|||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
sourcemapExcludeSources: true,
|
sourcemapExcludeSources: true,
|
||||||
banner: "#!/usr/bin/env node",
|
banner: "#!/usr/bin/env node",
|
||||||
file: outputFile,
|
file: outputFile
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as executable
|
// Mark as executable
|
||||||
@@ -297,54 +303,37 @@ async function generateFrontendJs(): Promise<void> {
|
|||||||
const inputFile = path.resolve(INPUT_DIR, "ui/app.ts");
|
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 inlineDeps = ["mithril", "parsimmon", "jsbi", "espresso-iisojs"];
|
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: inputFile,
|
input: inputFile,
|
||||||
external: externals.filter((e) => !inlineDeps.includes(e)),
|
external: externals,
|
||||||
plugins: [
|
plugins: [
|
||||||
rollupJson({ preferConst: true }),
|
rollupJson({ preferConst: true }),
|
||||||
{
|
typescript({ tsconfig: "./tsconfig.json" })
|
||||||
name: "",
|
|
||||||
resolveId: function (importee, importer) {
|
|
||||||
if (importee.endsWith("/bigint")) {
|
|
||||||
return this.resolve(importee + "-jsbi", importer);
|
|
||||||
} else if (importee === "espresso-iisojs") {
|
|
||||||
return this.resolve(
|
|
||||||
"espresso-iisojs/dist/espresso-iisojs-jsbi.mjs",
|
|
||||||
importer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
typescript({ tsconfig: "./tsconfig.json" }),
|
|
||||||
nodeResolve(),
|
|
||||||
commonjs(),
|
|
||||||
],
|
],
|
||||||
inlineDynamicImports: true,
|
inlineDynamicImports: true,
|
||||||
treeshake: {
|
treeshake: {
|
||||||
propertyReadSideEffects: false,
|
propertyReadSideEffects: false,
|
||||||
moduleSideEffects: false,
|
pureExternalModules: true
|
||||||
},
|
},
|
||||||
onwarn: ((warning, warn) => {
|
onwarn: ((warning, warn) => {
|
||||||
// Ignore circular dependency warnings
|
// Ignore circular dependency warnings
|
||||||
if (warning.code !== "CIRCULAR_DEPENDENCY") warn(warning);
|
if (warning.code !== "CIRCULAR_DEPENDENCY") warn(warning);
|
||||||
}) as WarningHandler,
|
}) as WarningHandler
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.write({
|
||||||
preferConst: true,
|
preferConst: true,
|
||||||
format: "es",
|
format: "esm",
|
||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
sourcemapExcludeSources: true,
|
sourcemapExcludeSources: true,
|
||||||
file: outputFile,
|
file: outputFile
|
||||||
});
|
});
|
||||||
|
|
||||||
const webpackConf = {
|
const webpackConf = {
|
||||||
mode: MODE,
|
mode: MODE,
|
||||||
entry: outputFile,
|
entry: outputFile,
|
||||||
resolve: {
|
resolve: {
|
||||||
aliasFields: ["module"],
|
aliasFields: ["module"]
|
||||||
},
|
},
|
||||||
devtool: "nosources-source-map",
|
devtool: "nosources-source-map",
|
||||||
module: {
|
module: {
|
||||||
@@ -352,14 +341,14 @@ async function generateFrontendJs(): Promise<void> {
|
|||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
use: ["source-map-loader"],
|
use: ["source-map-loader"],
|
||||||
enforce: "pre",
|
enforce: "pre"
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(OUTPUT_DIR, "public"),
|
path: path.resolve(OUTPUT_DIR, "public"),
|
||||||
filename: "app.js",
|
filename: "app.js"
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stats = await promisify(webpack)(webpackConf);
|
const stats = await promisify(webpack)(webpackConf);
|
||||||
@@ -390,15 +379,13 @@ init()
|
|||||||
generateIconsSprite(),
|
generateIconsSprite(),
|
||||||
generateToolsJs(),
|
generateToolsJs(),
|
||||||
generateBackendJs(),
|
generateBackendJs(),
|
||||||
generateFrontendJs(),
|
generateFrontendJs()
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {})
|
||||||
// Ignore
|
.catch(err => {
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
process.stderr.write(err.stack + "\n");
|
process.stderr.write(err.stack + "\n");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
process.stderr.write(err.stack + "\n");
|
process.stderr.write(err.stack + "\n");
|
||||||
});
|
});
|
||||||
@@ -164,13 +164,12 @@ FS_ACCESS_LOG_FILE
|
|||||||
|
|
||||||
Default: unset
|
Default: unset
|
||||||
|
|
||||||
FS_URL_PREFIX
|
FS_HOSTNAME
|
||||||
The URL prefix (e.g. 'https://example.com:7657/') to use when generating the
|
The hostname to use when generating the file URL for TR-069 Download
|
||||||
file URL for TR-069 Download requests. Set this if genieacs-fs and
|
requests. Set this if the hostname where genieacs-fs is listening differs
|
||||||
genieacs-cwmp are behind a proxy or running on different servers.
|
from the hostname in the ACS URL.
|
||||||
|
|
||||||
Default: auto generated based on the hostname from the ACS URL, FS_PORT
|
Default: the hostname from the ACS URL
|
||||||
config, and whether or not SSL is enabled for genieacs-fs.
|
|
||||||
|
|
||||||
UI_WORKER_PROCESSES
|
UI_WORKER_PROCESSES
|
||||||
The number of worker processes to spawn for genieacs-ui. A value of 0 means
|
The number of worker processes to spawn for genieacs-ui. A value of 0 means
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ differences between the scripts of provisions and virtual parameters are:
|
|||||||
|
|
||||||
.. code:: javascript
|
.. code:: javascript
|
||||||
|
|
||||||
// [<declared attr timestamps, declared attr values>, <current attr timestamps>, <current attr values>]
|
// [<current attr timestamps>, <current attr values>, <declared attr timestamps, declared attr values>]
|
||||||
[{path: 1559849387191, value: 1559849387191}, {value: ["new val", "xsd:string"]}, {path: 1559840000000, value: 1559840000000}, {value: ["cur val", "xsd:string"]}]
|
[{path: 1559840000000, value: 1559840000000}, {value: ["cur val", "xsd:string"]}, {path: 1559849387191, value: 1559849387191}, {value: ["new val", "xsd:string"]}]
|
||||||
|
|
||||||
- Virtual parameter scripts must return an object containing the attributes of
|
- Virtual parameter scripts must return an object containing the attributes of
|
||||||
this parameter.
|
this parameter.
|
||||||
@@ -69,9 +69,9 @@ Expose an external value as a virtual parameter
|
|||||||
|
|
||||||
// Example: Expose an external value as a virtual parameter
|
// Example: Expose an external value as a virtual parameter
|
||||||
let serial = declare("DeviceID.SerialNumber", {value: 1});
|
let serial = declare("DeviceID.SerialNumber", {value: 1});
|
||||||
if (args[1].value) {
|
if (values.value) {
|
||||||
ext("example-ext", "set", serial.value[0], args[1].value[0]);
|
ext("example-ext", "set", serial.value[0], values.value[0]);
|
||||||
return {writable: true, value: [args[1].value[0], "xsd:string"]};
|
return {writable: true, value: [values.value[0], "xsd:string"]};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let v = ext("example-ext", "get", serial.value[0]);
|
let v = ext("example-ext", "get", serial.value[0]);
|
||||||
@@ -85,8 +85,8 @@ Create an editable virtual parameter for WPA passphrase
|
|||||||
|
|
||||||
// Example: Create an editable virtual parameter for WPA passphrase
|
// Example: Create an editable virtual parameter for WPA passphrase
|
||||||
let m = "";
|
let m = "";
|
||||||
if (args[1].value) {
|
if (VALUES && VALUES.value) {
|
||||||
m = args[1].value[0];
|
m = VALUES.value[0];
|
||||||
declare("Device.WiFi.AccessPoint.1.Security.KeyPassphrase", null, {value: m});
|
declare("Device.WiFi.AccessPoint.1.Security.KeyPassphrase", null, {value: m});
|
||||||
declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", null, {value: m});
|
declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", null, {value: m});
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-104
@@ -24,84 +24,64 @@ import * as cache from "./cache";
|
|||||||
import {
|
import {
|
||||||
getCurrentSnapshot,
|
getCurrentSnapshot,
|
||||||
getConfig,
|
getConfig,
|
||||||
getConfigExpression,
|
getConfigExpression
|
||||||
} from "./local-cache";
|
} from "./local-cache";
|
||||||
import {
|
import {
|
||||||
httpConnectionRequest,
|
httpConnectionRequest,
|
||||||
udpConnectionRequest,
|
udpConnectionRequest
|
||||||
} from "./connection-request";
|
} from "./connection-request";
|
||||||
import { Expression, Task } from "./types";
|
import { Expression, Task } from "./types";
|
||||||
import { flattenDevice } from "./mongodb-functions";
|
|
||||||
import { evaluate } from "./common/expression";
|
|
||||||
|
|
||||||
export async function connectionRequest(
|
export async function connectionRequest(deviceId): Promise<void> {
|
||||||
deviceId: string,
|
const options = {
|
||||||
device?: Record<string, { value?: [boolean | number | string, string] }>
|
projection: {
|
||||||
): Promise<void> {
|
_deviceId: 1,
|
||||||
if (!device) {
|
"Device.ManagementServer.ConnectionRequestURL._value": 1,
|
||||||
const res = await db.devicesCollection.findOne({ _id: deviceId });
|
"Device.ManagementServer.UDPConnectionRequestAddress._value": 1,
|
||||||
if (!res) throw new Error("No such device");
|
"Device.ManagementServer.ConnectionRequestUsername._value": 1,
|
||||||
device = flattenDevice(res);
|
"Device.ManagementServer.ConnectionRequestPassword._value": 1,
|
||||||
}
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestURL._value": 1,
|
||||||
|
"InternetGatewayDevice.ManagementServer.UDPConnectionRequestAddress._value": 1,
|
||||||
let connectionRequestUrl, udpConnectionRequestAddress, username, password;
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername._value": 1,
|
||||||
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestPassword._value": 1
|
||||||
if (device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]) {
|
|
||||||
connectionRequestUrl = (device[
|
|
||||||
"InternetGatewayDevice.ManagementServer.ConnectionRequestURL"
|
|
||||||
].value || [""])[0];
|
|
||||||
udpConnectionRequestAddress = ((
|
|
||||||
device[
|
|
||||||
"InternetGatewayDevice.ManagementServer.UDPConnectionRequestAddress"
|
|
||||||
] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
username = ((
|
|
||||||
device[
|
|
||||||
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername"
|
|
||||||
] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
password = ((
|
|
||||||
device[
|
|
||||||
"InternetGatewayDevice.ManagementServer.ConnectionRequestPassword"
|
|
||||||
] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
} else {
|
|
||||||
connectionRequestUrl = (device[
|
|
||||||
"Device.ManagementServer.ConnectionRequestURL"
|
|
||||||
].value || [""])[0];
|
|
||||||
udpConnectionRequestAddress = ((
|
|
||||||
device["Device.ManagementServer.UDPConnectionRequestAddress"] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
username = ((
|
|
||||||
device["Device.ManagementServer.ConnectionRequestUsername"] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
password = ((
|
|
||||||
device["Device.ManagementServer.ConnectionRequestPassword"] || {}
|
|
||||||
).value || [""])[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteAddress = parse(connectionRequestUrl).host;
|
|
||||||
|
|
||||||
const evalCallback = (exp): Expression => {
|
|
||||||
if (!Array.isArray(exp)) return exp;
|
|
||||||
if (exp[0] === "PARAM" && typeof exp[1] === "string") {
|
|
||||||
let name = exp[1];
|
|
||||||
if (name === "id") name = "DeviceID.ID";
|
|
||||||
else if (name === "serialNumber") name = "DeviceID.SerialNumber";
|
|
||||||
else if (name === "productClass") name = "DeviceID.ProductClass";
|
|
||||||
else if (name === "oui") name = "DeviceID.OUI";
|
|
||||||
else if (name === "remoteAddress") return remoteAddress;
|
|
||||||
else if (name === "username") return username;
|
|
||||||
else if (name === "password") return password;
|
|
||||||
|
|
||||||
const p = device[name];
|
|
||||||
if (p && p.value) return p.value[0];
|
|
||||||
} else if (exp[0] === "FUNC") {
|
|
||||||
if (exp[1] === "REMOTE_ADDRESS") return remoteAddress;
|
|
||||||
else if (exp[1] === "USERNAME") return username;
|
|
||||||
else if (exp[1] === "PASSWORD") return password;
|
|
||||||
}
|
}
|
||||||
return exp;
|
};
|
||||||
|
|
||||||
|
const device = await db.devicesCollection.findOne({ _id: deviceId }, options);
|
||||||
|
if (!device) throw new Error("No such device");
|
||||||
|
|
||||||
|
let managementServer,
|
||||||
|
connectionRequestUrl,
|
||||||
|
udpConnectionRequestAddress,
|
||||||
|
username,
|
||||||
|
password;
|
||||||
|
if (device.Device)
|
||||||
|
// TR-181 data model
|
||||||
|
managementServer = device.Device.ManagementServer;
|
||||||
|
// TR-098 data model
|
||||||
|
else managementServer = device.InternetGatewayDevice.ManagementServer;
|
||||||
|
|
||||||
|
if (managementServer.ConnectionRequestURL)
|
||||||
|
connectionRequestUrl = managementServer.ConnectionRequestURL._value;
|
||||||
|
if (managementServer.UDPConnectionRequestAddress) {
|
||||||
|
udpConnectionRequestAddress =
|
||||||
|
managementServer.UDPConnectionRequestAddress._value;
|
||||||
|
}
|
||||||
|
if (managementServer.ConnectionRequestUsername)
|
||||||
|
username = managementServer.ConnectionRequestUsername._value;
|
||||||
|
if (managementServer.ConnectionRequestPassword)
|
||||||
|
password = managementServer.ConnectionRequestPassword._value;
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
id: device["_id"],
|
||||||
|
serialNumber: device["_deviceId"]["SerialNumber"],
|
||||||
|
productClass: device["_deviceId"]["ProductClass"],
|
||||||
|
oui: device["_deviceId"]["OUI"],
|
||||||
|
remoteAddress: connectionRequestUrl
|
||||||
|
? parse(connectionRequestUrl).host
|
||||||
|
: null,
|
||||||
|
username: username || "",
|
||||||
|
password: password || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const snapshot = await getCurrentSnapshot();
|
const snapshot = await getCurrentSnapshot();
|
||||||
@@ -109,23 +89,20 @@ export async function connectionRequest(
|
|||||||
const UDP_CONNECTION_REQUEST_PORT = +getConfig(
|
const UDP_CONNECTION_REQUEST_PORT = +getConfig(
|
||||||
snapshot,
|
snapshot,
|
||||||
"cwmp.udpConnectionRequestPort",
|
"cwmp.udpConnectionRequestPort",
|
||||||
{},
|
context,
|
||||||
now,
|
now
|
||||||
evalCallback
|
|
||||||
);
|
);
|
||||||
const CONNECTION_REQUEST_TIMEOUT = +getConfig(
|
const CONNECTION_REQUEST_TIMEOUT = +getConfig(
|
||||||
snapshot,
|
snapshot,
|
||||||
"cwmp.connectionRequestTimeout",
|
"cwmp.connectionRequestTimeout",
|
||||||
{},
|
context,
|
||||||
now,
|
now
|
||||||
evalCallback
|
|
||||||
);
|
);
|
||||||
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = !!getConfig(
|
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = !!getConfig(
|
||||||
snapshot,
|
snapshot,
|
||||||
"cwmp.connectionRequestAllowBasicAuth",
|
"cwmp.connectionRequestAllowBasicAuth",
|
||||||
{},
|
context,
|
||||||
now,
|
now
|
||||||
evalCallback
|
|
||||||
);
|
);
|
||||||
let authExp: Expression = getConfigExpression(
|
let authExp: Expression = getConfigExpression(
|
||||||
snapshot,
|
snapshot,
|
||||||
@@ -137,19 +114,18 @@ export async function connectionRequest(
|
|||||||
"FUNC",
|
"FUNC",
|
||||||
"AUTH",
|
"AUTH",
|
||||||
["PARAM", "username"],
|
["PARAM", "username"],
|
||||||
["PARAM", "password"],
|
["PARAM", "password"]
|
||||||
] as Expression;
|
] as Expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
authExp = evaluate(authExp, {}, now, evalCallback);
|
const debug = !!getConfig(snapshot, "cwmp.debug", context, now);
|
||||||
|
|
||||||
const debug = !!getConfig(snapshot, "cwmp.debug", {}, now, evalCallback);
|
|
||||||
|
|
||||||
let udpProm;
|
let udpProm;
|
||||||
if (udpConnectionRequestAddress) {
|
if (udpConnectionRequestAddress) {
|
||||||
udpProm = udpConnectionRequest(
|
udpProm = udpConnectionRequest(
|
||||||
udpConnectionRequestAddress,
|
udpConnectionRequestAddress,
|
||||||
authExp,
|
authExp,
|
||||||
|
context,
|
||||||
UDP_CONNECTION_REQUEST_PORT,
|
UDP_CONNECTION_REQUEST_PORT,
|
||||||
debug,
|
debug,
|
||||||
deviceId
|
deviceId
|
||||||
@@ -160,6 +136,7 @@ export async function connectionRequest(
|
|||||||
await httpConnectionRequest(
|
await httpConnectionRequest(
|
||||||
connectionRequestUrl,
|
connectionRequestUrl,
|
||||||
authExp,
|
authExp,
|
||||||
|
context,
|
||||||
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
|
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
|
||||||
CONNECTION_REQUEST_TIMEOUT,
|
CONNECTION_REQUEST_TIMEOUT,
|
||||||
debug,
|
debug,
|
||||||
@@ -171,12 +148,8 @@ export async function connectionRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function watchTask(
|
export async function watchTask(deviceId, taskId, timeout): Promise<string> {
|
||||||
deviceId: string,
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
taskId: string,
|
|
||||||
timeout: number
|
|
||||||
): Promise<string> {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
const task = await db.tasksCollection.findOne(
|
const task = await db.tasksCollection.findOne(
|
||||||
{ _id: taskId },
|
{ _id: taskId },
|
||||||
@@ -186,7 +159,7 @@ export async function watchTask(
|
|||||||
|
|
||||||
const q = { _id: `${deviceId}:task_${taskId}` };
|
const q = { _id: `${deviceId}:task_${taskId}` };
|
||||||
const fault = await db.faultsCollection.findOne(q, {
|
const fault = await db.faultsCollection.findOne(q, {
|
||||||
projection: { _id: 1 },
|
projection: { _id: 1 }
|
||||||
});
|
});
|
||||||
if (fault) return "fault";
|
if (fault) return "fault";
|
||||||
|
|
||||||
@@ -198,7 +171,7 @@ export async function watchTask(
|
|||||||
function sanitizeTask(task): void {
|
function sanitizeTask(task): void {
|
||||||
task.timestamp = new Date(task.timestamp || Date.now());
|
task.timestamp = new Date(task.timestamp || Date.now());
|
||||||
if (task.expiry) {
|
if (task.expiry) {
|
||||||
if (task.expiry instanceof Date || isNaN(task.expiry))
|
if (common.typeOf(task.expiry) === common.DATE_TYPE || isNaN(task.expiry))
|
||||||
task.expiry = new Date(task.expiry);
|
task.expiry = new Date(task.expiry);
|
||||||
else task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000);
|
else task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000);
|
||||||
}
|
}
|
||||||
@@ -272,10 +245,9 @@ function sanitizeTask(task): void {
|
|||||||
case "provisions":
|
case "provisions":
|
||||||
if (
|
if (
|
||||||
!Array.isArray(task.provisions) ||
|
!Array.isArray(task.provisions) ||
|
||||||
!task.provisions.every((arr) =>
|
!task.provisions.every(arr =>
|
||||||
arr.every(
|
arr.every(
|
||||||
(s) =>
|
s => s == null || ["boolean", "number", "string"].includes(typeof s)
|
||||||
s == null || ["boolean", "number", "string"].includes(typeof s)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -286,8 +258,8 @@ function sanitizeTask(task): void {
|
|||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function insertTasks(tasks: any[]): Promise<Task[]> {
|
export async function insertTasks(tasks): Promise<Task[]> {
|
||||||
if (tasks && !Array.isArray(tasks)) tasks = [tasks];
|
if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) tasks = [tasks];
|
||||||
else if (!tasks || tasks.length === 0) return tasks || [];
|
else if (!tasks || tasks.length === 0) return tasks || [];
|
||||||
|
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
@@ -295,7 +267,7 @@ export async function insertTasks(tasks: any[]): Promise<Task[]> {
|
|||||||
if (task.uniqueKey) {
|
if (task.uniqueKey) {
|
||||||
await db.tasksCollection.deleteOne({
|
await db.tasksCollection.deleteOne({
|
||||||
device: task.device,
|
device: task.device,
|
||||||
uniqueKey: task.uniqueKey,
|
uniqueKey: task.uniqueKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,20 +275,20 @@ export async function insertTasks(tasks: any[]): Promise<Task[]> {
|
|||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteDevice(deviceId: string): Promise<void> {
|
export async function deleteDevice(deviceId): Promise<void> {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
db.tasksCollection.deleteMany({ device: deviceId }),
|
db.tasksCollection.deleteMany({ device: deviceId }),
|
||||||
db.devicesCollection.deleteOne({ _id: deviceId }),
|
db.devicesCollection.deleteOne({ _id: deviceId }),
|
||||||
db.faultsCollection.deleteMany({
|
db.faultsCollection.deleteMany({
|
||||||
_id: {
|
_id: {
|
||||||
$regex: `^${common.escapeRegExp(deviceId)}\\:`,
|
$regex: `^${common.escapeRegExp(deviceId)}\\:`
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
db.operationsCollection.deleteMany({
|
db.operationsCollection.deleteMany({
|
||||||
_id: {
|
_id: {
|
||||||
$regex: `^${common.escapeRegExp(deviceId)}\\:`,
|
$regex: `^${common.escapeRegExp(deviceId)}\\:`
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
cache.del(`${deviceId}_tasks_faults_operations`),
|
cache.del(`${deviceId}_tasks_faults_operations`)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
+23
-18
@@ -19,17 +19,15 @@
|
|||||||
|
|
||||||
import { createHash, randomBytes, pbkdf2 } from "crypto";
|
import { createHash, randomBytes, pbkdf2 } from "crypto";
|
||||||
|
|
||||||
function parseHeaderFeilds(str: string): Record<string, string> {
|
function parseHeaderFeilds(str: string): {} {
|
||||||
const res = {};
|
const res = {};
|
||||||
const parts = str.split(",");
|
const parts = str.split(",");
|
||||||
|
|
||||||
let part: string;
|
let part;
|
||||||
while ((part = parts.shift()) != null) {
|
while ((part = parts.shift()) != null) {
|
||||||
const name = part.split("=", 1)[0];
|
const name = part.split("=", 1)[0];
|
||||||
if (name.length === part.length) {
|
if (name.length === part.length)
|
||||||
if (!part.trim()) continue;
|
|
||||||
throw new Error("Unable to parse auth header");
|
throw new Error("Unable to parse auth header");
|
||||||
}
|
|
||||||
|
|
||||||
let value = part.slice(name.length + 1);
|
let value = part.slice(name.length + 1);
|
||||||
if (!/^\s*"/.test(value)) {
|
if (!/^\s*"/.test(value)) {
|
||||||
@@ -52,9 +50,7 @@ function parseHeaderFeilds(str: string): Record<string, string> {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAuthorizationHeader(
|
export function parseAuthorizationHeader(authHeader): { method: string } {
|
||||||
authHeader: string
|
|
||||||
): { method: string } {
|
|
||||||
authHeader = authHeader.trim();
|
authHeader = authHeader.trim();
|
||||||
const method = authHeader.split(" ", 1)[0];
|
const method = authHeader.split(" ", 1)[0];
|
||||||
const res = { method: method };
|
const res = { method: method };
|
||||||
@@ -76,9 +72,7 @@ export function parseAuthorizationHeader(
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseWwwAuthenticateHeader(
|
export function parseWwwAuthenticateHeader(authHeader): {} {
|
||||||
authHeader: string
|
|
||||||
): Record<string, string> {
|
|
||||||
authHeader = authHeader.trim();
|
authHeader = authHeader.trim();
|
||||||
const method = authHeader.split(" ", 1)[0];
|
const method = authHeader.split(" ", 1)[0];
|
||||||
const res = { method: method };
|
const res = { method: method };
|
||||||
@@ -86,7 +80,7 @@ export function parseWwwAuthenticateHeader(
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function basic(username: string, password: string): string {
|
export function basic(username, password): string {
|
||||||
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,12 +97,20 @@ export function digest(
|
|||||||
nc?: string | Buffer
|
nc?: string | Buffer
|
||||||
): string {
|
): string {
|
||||||
const ha1 = createHash("md5");
|
const ha1 = createHash("md5");
|
||||||
ha1.update(username).update(":").update(realm).update(":").update(password);
|
ha1
|
||||||
|
.update(username)
|
||||||
|
.update(":")
|
||||||
|
.update(realm)
|
||||||
|
.update(":")
|
||||||
|
.update(password);
|
||||||
// TODO support "MD5-sess" algorithm directive
|
// TODO support "MD5-sess" algorithm directive
|
||||||
const ha1d = ha1.digest("hex");
|
const ha1d = ha1.digest("hex");
|
||||||
|
|
||||||
const ha2 = createHash("md5");
|
const ha2 = createHash("md5");
|
||||||
ha2.update(httpMethod).update(":").update(uri);
|
ha2
|
||||||
|
.update(httpMethod)
|
||||||
|
.update(":")
|
||||||
|
.update(uri);
|
||||||
|
|
||||||
if (qop === "auth-int") {
|
if (qop === "auth-int") {
|
||||||
const bodyHash = createHash("md5")
|
const bodyHash = createHash("md5")
|
||||||
@@ -120,7 +122,10 @@ export function digest(
|
|||||||
const ha2d = ha2.digest("hex");
|
const ha2d = ha2.digest("hex");
|
||||||
|
|
||||||
const hash = createHash("md5");
|
const hash = createHash("md5");
|
||||||
hash.update(ha1d).update(":").update(nonce);
|
hash
|
||||||
|
.update(ha1d)
|
||||||
|
.update(":")
|
||||||
|
.update(nonce);
|
||||||
if (qop) {
|
if (qop) {
|
||||||
hash
|
hash
|
||||||
.update(":")
|
.update(":")
|
||||||
@@ -141,7 +146,7 @@ export function solveDigest(
|
|||||||
uri: string | Buffer,
|
uri: string | Buffer,
|
||||||
httpMethod: string | Buffer,
|
httpMethod: string | Buffer,
|
||||||
body: string | Buffer,
|
body: string | Buffer,
|
||||||
authHeader: Record<string, string>
|
authHeader
|
||||||
): string {
|
): string {
|
||||||
const cnonce = randomBytes(8).toString("hex");
|
const cnonce = randomBytes(8).toString("hex");
|
||||||
const nc = "00000001";
|
const nc = "00000001";
|
||||||
@@ -178,7 +183,7 @@ export function solveDigest(
|
|||||||
return authString;
|
return authString;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateSalt(length: number): Promise<string> {
|
export function generateSalt(length): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
randomBytes(length, (err, rand) => {
|
randomBytes(length, (err, rand) => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
@@ -187,7 +192,7 @@ export function generateSalt(length: number): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hashPassword(pass: string, salt: string): Promise<string> {
|
export function hashPassword(pass, salt): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
pbkdf2(pass, salt, 10000, 128, "sha512", (err, hash) => {
|
pbkdf2(pass, salt, 10000, 128, "sha512", (err, hash) => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
|
|||||||
+12
-14
@@ -29,8 +29,7 @@ let mongoTimeOffset = 0;
|
|||||||
export async function connect(): Promise<void> {
|
export async function connect(): Promise<void> {
|
||||||
const MONGODB_CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL");
|
const MONGODB_CONNECTION_URL = "" + config.get("MONGODB_CONNECTION_URL");
|
||||||
clientPromise = MongoClient.connect(MONGODB_CONNECTION_URL, {
|
clientPromise = MongoClient.connect(MONGODB_CONNECTION_URL, {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true
|
||||||
useUnifiedTopology: true,
|
|
||||||
});
|
});
|
||||||
const db = (await clientPromise).db();
|
const db = (await clientPromise).db();
|
||||||
mongoCollection = db.collection("cache");
|
mongoCollection = db.collection("cache");
|
||||||
@@ -44,7 +43,7 @@ export async function disconnect(): Promise<void> {
|
|||||||
if (clientPromise) await (await clientPromise).close();
|
if (clientPromise) await (await clientPromise).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(key: string | string[]): Promise<any> {
|
export async function get(key): Promise<any> {
|
||||||
const expire = new Date(Date.now() - mongoTimeOffset);
|
const expire = new Date(Date.now() - mongoTimeOffset);
|
||||||
if (Array.isArray(key)) {
|
if (Array.isArray(key)) {
|
||||||
const res = await mongoCollection.find({ _id: { $in: key } }).toArray();
|
const res = await mongoCollection.find({ _id: { $in: key } }).toArray();
|
||||||
@@ -55,7 +54,7 @@ export async function get(key: string | string[]): Promise<any> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const values = [];
|
const values = [];
|
||||||
res.forEach((r) => {
|
res.forEach(r => {
|
||||||
if (r["expire"] > expire) values[indices[r["_id"]]] = r["value"];
|
if (r["expire"] > expire) values[indices[r["_id"]]] = r["value"];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ export async function get(key: string | string[]): Promise<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(key: string | string[]): Promise<void> {
|
export async function del(key): Promise<void> {
|
||||||
if (Array.isArray(key))
|
if (Array.isArray(key))
|
||||||
await mongoCollection.deleteMany({ _id: { $in: key } });
|
await mongoCollection.deleteMany({ _id: { $in: key } });
|
||||||
else await mongoCollection.deleteOne({ _id: key });
|
else await mongoCollection.deleteOne({ _id: key });
|
||||||
@@ -86,7 +85,7 @@ export async function set(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pop(key: string): Promise<any> {
|
export async function pop(key): Promise<any> {
|
||||||
const res = await mongoCollection.findOneAndDelete({ _id: key });
|
const res = await mongoCollection.findOneAndDelete({ _id: key });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -99,17 +98,16 @@ export async function pop(key: string): Promise<any> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function lock(
|
export async function lock(lockName, ttl): Promise<Function> {
|
||||||
lockName: string,
|
const token = Math.random()
|
||||||
ttl: number
|
.toString(36)
|
||||||
): Promise<(extendTtl?: number) => Promise<void>> {
|
.slice(2);
|
||||||
const token = Math.random().toString(36).slice(2);
|
|
||||||
|
|
||||||
async function unlockOrExtend(extendTtl?: number): Promise<void> {
|
async function unlockOrExtend(extendTtl): Promise<void> {
|
||||||
if (!extendTtl) {
|
if (!extendTtl) {
|
||||||
const res = await mongoCollection.deleteOne({
|
const res = await mongoCollection.deleteOne({
|
||||||
_id: lockName,
|
_id: lockName,
|
||||||
value: token,
|
value: token
|
||||||
});
|
});
|
||||||
if (res["result"]["n"] !== 1) throw new Error("Lock expired");
|
if (res["result"]["n"] !== 1) throw new Error("Lock expired");
|
||||||
} else {
|
} else {
|
||||||
@@ -133,7 +131,7 @@ export async function lock(
|
|||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err && err.code === 11000) {
|
if (err && err.code === 11000) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
return lock(lockName, ttl);
|
return lock(lockName, ttl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-9
@@ -41,7 +41,7 @@ function restartWorker(worker, code, signal): void {
|
|||||||
message: "Worker died",
|
message: "Worker died",
|
||||||
pid: worker.process.pid,
|
pid: worker.process.pid,
|
||||||
exitCode: null,
|
exitCode: null,
|
||||||
signal: null,
|
signal: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (code != null) msg.exitCode = code;
|
if (code != null) msg.exitCode = code;
|
||||||
@@ -57,7 +57,7 @@ function restartWorker(worker, code, signal): void {
|
|||||||
min2 = 0,
|
min2 = 0,
|
||||||
min3 = 0;
|
min3 = 0;
|
||||||
|
|
||||||
crashes = crashes.filter((n) => {
|
crashes = crashes.filter(n => {
|
||||||
if (n > now - 60000) ++min1;
|
if (n > now - 60000) ++min1;
|
||||||
else if (n > now - 120000) ++min2;
|
else if (n > now - 120000) ++min2;
|
||||||
else if (n > now - 180000) ++min3;
|
else if (n > now - 180000) ++min3;
|
||||||
@@ -72,7 +72,7 @@ function restartWorker(worker, code, signal): void {
|
|||||||
|
|
||||||
logger.error({
|
logger.error({
|
||||||
message: "Too many crashes, exiting",
|
message: "Too many crashes, exiting",
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,11 +89,7 @@ function restartWorker(worker, code, signal): void {
|
|||||||
}, respawnTimestamp - now);
|
}, respawnTimestamp - now);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(
|
export function start(workerCount, servicePort, serviceAddress): void {
|
||||||
workerCount: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceAddress: string
|
|
||||||
): void {
|
|
||||||
cluster.on("listening", (worker, address) => {
|
cluster.on("listening", (worker, address) => {
|
||||||
if (
|
if (
|
||||||
(address.addressType === 4 || address.addressType === 6) &&
|
(address.addressType === 4 || address.addressType === 6) &&
|
||||||
@@ -104,7 +100,7 @@ export function start(
|
|||||||
message: "Worker listening",
|
message: "Worker listening",
|
||||||
pid: worker.process.pid,
|
pid: worker.process.pid,
|
||||||
address: address.address,
|
address: address.address,
|
||||||
port: address.port,
|
port: address.port
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-5
@@ -17,12 +17,22 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function generateDeviceId(
|
export const UNDEFINED_TYPE = "[object Undefined]";
|
||||||
deviceIdStruct: Record<string, string>
|
export const NULL_TYPE = "[object Null]";
|
||||||
): string {
|
export const BOOLEAN_TYPE = "[object Boolean]";
|
||||||
|
export const NUMBER_TYPE = "[object Number]";
|
||||||
|
export const STRING_TYPE = "[object String]";
|
||||||
|
export const ARRAY_TYPE = "[object Array]";
|
||||||
|
export const OBJECT_TYPE = "[object Object]";
|
||||||
|
export const REGEXP_TYPE = "[object RegExp]";
|
||||||
|
export const DATE_TYPE = "[object Date]";
|
||||||
|
|
||||||
|
export const typeOf = (obj): string => Object.prototype.toString.call(obj);
|
||||||
|
|
||||||
|
export function generateDeviceId(deviceIdStruct): string {
|
||||||
// Escapes everything except alphanumerics and underscore
|
// Escapes everything except alphanumerics and underscore
|
||||||
function esc(str): string {
|
function esc(str): string {
|
||||||
return str.replace(/[^A-Za-z0-9_]/g, (chr) => {
|
return str.replace(/[^A-Za-z0-9_]/g, chr => {
|
||||||
const buf = Buffer.from(chr, "utf8");
|
const buf = Buffer.from(chr, "utf8");
|
||||||
let rep = "";
|
let rep = "";
|
||||||
for (const b of buf) rep += "%" + b.toString(16).toUpperCase();
|
for (const b of buf) rep += "%" + b.toString(16).toUpperCase();
|
||||||
@@ -44,6 +54,6 @@ export function generateDeviceId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Source: http://stackoverflow.com/a/6969486
|
// Source: http://stackoverflow.com/a/6969486
|
||||||
export function escapeRegExp(str: string): string {
|
export function escapeRegExp(str): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
@@ -23,13 +23,13 @@ import { evaluate, or } from "./expression";
|
|||||||
export default class Authorizer {
|
export default class Authorizer {
|
||||||
private permissionSets: PermissionSet[];
|
private permissionSets: PermissionSet[];
|
||||||
private validatorCache: WeakMap<
|
private validatorCache: WeakMap<
|
||||||
any,
|
object,
|
||||||
(mutationType, mutation, any) => boolean
|
(mutationType, mutation, any) => boolean
|
||||||
>;
|
>;
|
||||||
private hasAccessCache: Map<string, boolean>;
|
private hasAccessCache: Map<string, boolean>;
|
||||||
private getFilterCache: Map<string, Expression>;
|
private getFilterCache: Map<string, Expression>;
|
||||||
|
|
||||||
public constructor(permissionSets: PermissionSet[]) {
|
public constructor(permissionSets) {
|
||||||
this.permissionSets = permissionSets;
|
this.permissionSets = permissionSets;
|
||||||
this.validatorCache = new WeakMap();
|
this.validatorCache = new WeakMap();
|
||||||
this.hasAccessCache = new Map();
|
this.hasAccessCache = new Map();
|
||||||
@@ -77,8 +77,8 @@ export default class Authorizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getValidator(
|
public getValidator(
|
||||||
resourceType: string,
|
resourceType,
|
||||||
resource: unknown
|
resource
|
||||||
): (mutationType: string, mutation?: any, args?: any) => boolean {
|
): (mutationType: string, mutation?: any, args?: any) => boolean {
|
||||||
if (this.validatorCache.has(resource))
|
if (this.validatorCache.has(resource))
|
||||||
return this.validatorCache.get(resource);
|
return this.validatorCache.get(resource);
|
||||||
@@ -108,7 +108,7 @@ export default class Authorizer {
|
|||||||
mutation,
|
mutation,
|
||||||
resourceType,
|
resourceType,
|
||||||
object: resource,
|
object: resource,
|
||||||
options: any,
|
options: any
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueFunction = (paramName): any => {
|
const valueFunction = (paramName): any => {
|
||||||
@@ -124,8 +124,7 @@ export default class Authorizer {
|
|||||||
if (value == null) break;
|
if (value == null) break;
|
||||||
}
|
}
|
||||||
} else if (object[entry]) {
|
} else if (object[entry]) {
|
||||||
if (paramName) value = object[entry][paramName];
|
value = object[entry][paramName];
|
||||||
else value = object[entry];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2013-2020 GenieACS Inc.
|
|
||||||
*
|
|
||||||
* This file is part of GenieACS.
|
|
||||||
*
|
|
||||||
* GenieACS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* GenieACS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import JSBI from "jsbi";
|
|
||||||
|
|
||||||
export function and(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.bitwiseAnd(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function or(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.bitwiseOr(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function xor(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.bitwiseXor(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function not(a: JSBI): JSBI {
|
|
||||||
return JSBI.bitwiseNot(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lshift(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.leftShift(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rshift(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.signedRightShift(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function add(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.add(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sub(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.subtract(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mul(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.multiply(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function div(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.divide(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function exp(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.exponentiate(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rem(a: JSBI, b: JSBI): JSBI {
|
|
||||||
return JSBI.remainder(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toNumber(a: JSBI): number {
|
|
||||||
return JSBI.toNumber(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function eq(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.equal(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ne(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.notEqual(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lt(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.lessThan(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lte(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.lessThanOrEqual(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gt(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.greaterThan(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gte(a: JSBI, b: JSBI): boolean {
|
|
||||||
return JSBI.greaterThanOrEqual(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asUintN(a: number, b: JSBI): JSBI {
|
|
||||||
return JSBI.asUintN(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asIntN(a: number, b: JSBI): JSBI {
|
|
||||||
return JSBI.asIntN(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _BigInt = JSBI.BigInt;
|
|
||||||
type _bigint = JSBI;
|
|
||||||
export { _BigInt as BigInt, _bigint as bigint };
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2013-2020 GenieACS Inc.
|
|
||||||
*
|
|
||||||
* This file is part of GenieACS.
|
|
||||||
*
|
|
||||||
* GenieACS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* GenieACS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function and(a: bigint, b: bigint): bigint {
|
|
||||||
return a & b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function or(a: bigint, b: bigint): bigint {
|
|
||||||
return a | b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function xor(a: bigint, b: bigint): bigint {
|
|
||||||
return a ^ b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function not(a: bigint): bigint {
|
|
||||||
return ~a;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lshift(a: bigint, b: bigint): bigint {
|
|
||||||
return a << b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rshift(a: bigint, b: bigint): bigint {
|
|
||||||
return a >> b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function add(a: bigint, b: bigint): bigint {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sub(a: bigint, b: bigint): bigint {
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mul(a: bigint, b: bigint): bigint {
|
|
||||||
return a * b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function div(a: bigint, b: bigint): bigint {
|
|
||||||
return a / b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function exp(a: bigint, b: bigint): bigint {
|
|
||||||
return a ** b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rem(a: bigint, b: bigint): bigint {
|
|
||||||
return a % b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toNumber(a: bigint): number {
|
|
||||||
return Number(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function eq(a: bigint, b: bigint): boolean {
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ne(a: bigint, b: bigint): boolean {
|
|
||||||
return a !== b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lt(a: bigint, b: bigint): boolean {
|
|
||||||
return a < b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lte(a: bigint, b: bigint): boolean {
|
|
||||||
return a <= b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gt(a: bigint, b: bigint): boolean {
|
|
||||||
return a > b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gte(a: bigint, b: bigint): boolean {
|
|
||||||
return a >= b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asUintN(a: number, b: bigint): bigint {
|
|
||||||
return BigInt.asUintN(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asIntN(a: number, b: bigint): bigint {
|
|
||||||
return BigInt.asIntN(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _BigInt = BigInt;
|
|
||||||
type _bigint = bigint;
|
|
||||||
export { _BigInt as BigInt, _bigint as bigint };
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,285 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
|
*
|
||||||
|
* This file is part of GenieACS.
|
||||||
|
*
|
||||||
|
* GenieACS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* GenieACS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { map, parseLikePattern } from "./expression-parser";
|
||||||
|
import { Expression } from "../types";
|
||||||
|
|
||||||
|
type CNF = number[][];
|
||||||
|
|
||||||
|
function or(cnf1: CNF, cnf2: CNF): CNF {
|
||||||
|
if (cnf1.length === 0) return cnf2;
|
||||||
|
else if (cnf2.length === 0) return cnf1;
|
||||||
|
|
||||||
|
if (cnf1.length > cnf2.length) {
|
||||||
|
const c = cnf1;
|
||||||
|
cnf1 = cnf2;
|
||||||
|
cnf2 = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = [];
|
||||||
|
for (let i = 0; i < cnf1.length; ++i)
|
||||||
|
for (let j = i; j < cnf2.length; ++j) res.push(cnf1[i].concat(cnf2[j]));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 likePatterns = new Map();
|
||||||
|
map(exp, e => {
|
||||||
|
if (!Array.isArray(e)) return e;
|
||||||
|
if (e[0] === "LIKE" || e[0] === "NOT LIKE") {
|
||||||
|
if (
|
||||||
|
typeof e[2] === "string" &&
|
||||||
|
(e.length >= 4 || (e[3] != null && !Array.isArray(e[3])))
|
||||||
|
) {
|
||||||
|
const key = JSON.stringify(e[1]);
|
||||||
|
let s = likePatterns.get(key);
|
||||||
|
if (!s) likePatterns.set(key, (s = new Map()));
|
||||||
|
s.set(`${e[3] || ""}:${e[2]}`, parseLikePattern(e[2], e[3]));
|
||||||
|
}
|
||||||
|
} else if ([">", ">=", "<", "<=", "=", "<>"].includes(e[0])) {
|
||||||
|
if (!Array.isArray(e[2])) {
|
||||||
|
const key = `${JSON.stringify(e[1])}:${typeof e[2]}`;
|
||||||
|
let s = ranges.get(key);
|
||||||
|
if (!s) ranges.set(key, (s = new Set()));
|
||||||
|
s.add(e[2]);
|
||||||
|
} else if (!Array.isArray(e[1])) {
|
||||||
|
const key = `${JSON.stringify(e[2])}:${typeof e[1]}`;
|
||||||
|
let s = ranges.get(key);
|
||||||
|
if (!s) ranges.set(key, (s = new Set()));
|
||||||
|
s.add(e[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [k, v] of ranges) {
|
||||||
|
if (k.endsWith(":number")) {
|
||||||
|
ranges.set(
|
||||||
|
k,
|
||||||
|
Array.from(v as number[]).sort((a: number, b: number) => a - b)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ranges.set(k, Array.from(v).sort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expressions: Map<string, number> = new Map();
|
||||||
|
const mutuallyExclusive: Map<string, Set<number>> = new Map();
|
||||||
|
|
||||||
|
function getVariable(lhs, op, rhs = ""): number {
|
||||||
|
const key = `${lhs}:${op}:${rhs}`;
|
||||||
|
let f = expressions.get(key);
|
||||||
|
if (!f) {
|
||||||
|
expressions.set(key, (f = expressions.size + 1));
|
||||||
|
let m = mutuallyExclusive.get(lhs);
|
||||||
|
if (!m) mutuallyExclusive.set(lhs, (m = new Set()));
|
||||||
|
m.add(f);
|
||||||
|
if (rhs || op !== "null") m.add(getVariable(lhs, "null"));
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComparisonVariables(
|
||||||
|
lhs,
|
||||||
|
from,
|
||||||
|
fromInclusive,
|
||||||
|
to,
|
||||||
|
toInclusive
|
||||||
|
): number[] {
|
||||||
|
const type = from != null ? typeof from : typeof to;
|
||||||
|
const range = ranges.get(`${lhs}:${type}`);
|
||||||
|
const vars = [];
|
||||||
|
|
||||||
|
if (from == null) vars.push(`${lhs}:--${range[0]}`);
|
||||||
|
|
||||||
|
for (let i = 0; i < range.length; ++i) {
|
||||||
|
if (
|
||||||
|
i > 0 &&
|
||||||
|
(from == null || range[i] > from) &&
|
||||||
|
(to == null || range[i] <= to)
|
||||||
|
)
|
||||||
|
vars.push(`${lhs}:${range[i - 1]}--${range[i]}`);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(range[i] > from && range[i] < to) ||
|
||||||
|
(range[i] === from && fromInclusive) ||
|
||||||
|
(range[i] === to && toInclusive)
|
||||||
|
)
|
||||||
|
vars.push(`${lhs}:${range[i]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to == null) vars.push(`${lhs}:${range[range.length - 1]}--`);
|
||||||
|
|
||||||
|
return vars.map(key => {
|
||||||
|
let f = expressions.get(key);
|
||||||
|
if (!f) {
|
||||||
|
expressions.set(key, (f = expressions.size + 1));
|
||||||
|
let m = mutuallyExclusive.get(lhs);
|
||||||
|
if (!m) mutuallyExclusive.set(lhs, (m = new Set()));
|
||||||
|
m.add(f);
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLikeVariables(lhs, pat, esc): number[] {
|
||||||
|
const vars = [];
|
||||||
|
const pats = likePatterns.get(lhs);
|
||||||
|
pat = pats.get(`${esc || ""}:${pat}`);
|
||||||
|
for (const p of pats.values()) {
|
||||||
|
if (pat === p || likePatternIncludes(pat, p))
|
||||||
|
vars.push(`${lhs}:like:${JSON.stringify(p)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars.map(key => {
|
||||||
|
let f = expressions.get(key);
|
||||||
|
if (!f) expressions.set(key, (f = expressions.size + 1));
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComparisonOperators {
|
||||||
|
[name: string]: (v: string) => [string, boolean, string, boolean];
|
||||||
|
}
|
||||||
|
|
||||||
|
const comparisonOperators: ComparisonOperators = {
|
||||||
|
"=": v => [v, true, v, true],
|
||||||
|
">": v => [v, false, null, null],
|
||||||
|
">=": v => [v, true, null, null],
|
||||||
|
"<": v => [null, null, v, false],
|
||||||
|
"<=": v => [null, null, v, true]
|
||||||
|
};
|
||||||
|
|
||||||
|
function recursive(clause, negate): CNF {
|
||||||
|
let op = clause[0];
|
||||||
|
|
||||||
|
if ((op === "AND" && !negate) || (op === "OR" && negate)) {
|
||||||
|
let res = [];
|
||||||
|
for (let i = 1; i < clause.length; ++i)
|
||||||
|
res = res.concat(recursive(clause[i], negate));
|
||||||
|
return res;
|
||||||
|
} else if ((op === "OR" && !negate) || (op === "AND" && negate)) {
|
||||||
|
let res = [];
|
||||||
|
for (let i = 1; i < clause.length; ++i)
|
||||||
|
res = or(res, recursive(clause[i], negate));
|
||||||
|
return res;
|
||||||
|
} else if (op === "NOT") {
|
||||||
|
return recursive(clause[1], !negate);
|
||||||
|
} else if (op === "<>") {
|
||||||
|
const vars = getComparisonVariables(
|
||||||
|
JSON.stringify(clause[1]),
|
||||||
|
...comparisonOperators["="](clause[2])
|
||||||
|
);
|
||||||
|
if (!negate) return vars.map(x => [0 - x]);
|
||||||
|
else return [vars];
|
||||||
|
} else if (op === "IS NULL") {
|
||||||
|
const v = getVariable(JSON.stringify(clause[1]), "null");
|
||||||
|
if (negate) return [[0 - v]];
|
||||||
|
else return [[v]];
|
||||||
|
} else if (op === "IS NOT NULL") {
|
||||||
|
const v = getVariable(JSON.stringify(clause[1]), "null");
|
||||||
|
if (!negate) return [[0 - v]];
|
||||||
|
else return [[v]];
|
||||||
|
} else if (op in comparisonOperators) {
|
||||||
|
if (!Array.isArray(clause[2])) {
|
||||||
|
const vars = getComparisonVariables(
|
||||||
|
JSON.stringify(clause[1]),
|
||||||
|
...comparisonOperators[op](clause[2])
|
||||||
|
);
|
||||||
|
if (negate) return vars.map(x => [0 - x]);
|
||||||
|
else return [vars];
|
||||||
|
} else if (!Array.isArray(clause[1])) {
|
||||||
|
op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op;
|
||||||
|
const vars = getComparisonVariables(
|
||||||
|
JSON.stringify(clause[2]),
|
||||||
|
...comparisonOperators[op](clause[1])
|
||||||
|
);
|
||||||
|
if (negate) return vars.map(x => [0 - x]);
|
||||||
|
else return [vars];
|
||||||
|
} else {
|
||||||
|
let lhs = JSON.stringify(clause[1]);
|
||||||
|
let rhs = JSON.stringify(clause[2]);
|
||||||
|
// For consistency
|
||||||
|
if (rhs > lhs) {
|
||||||
|
const t = rhs;
|
||||||
|
rhs = lhs;
|
||||||
|
lhs = t;
|
||||||
|
op = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[op] || op;
|
||||||
|
}
|
||||||
|
|
||||||
|
const v = getVariable(lhs, op, rhs);
|
||||||
|
if (negate) return [[0 - v]];
|
||||||
|
else return [[v]];
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(op === "LIKE" || op === "NOT LIKE") &&
|
||||||
|
!Array.isArray(clause[2]) &&
|
||||||
|
clause[2] != null &&
|
||||||
|
(clause.length >= 4 || clause[3] != null)
|
||||||
|
) {
|
||||||
|
if (op === "NOT LIKE") negate = !negate;
|
||||||
|
const vars = getLikeVariables(
|
||||||
|
JSON.stringify(clause[1]),
|
||||||
|
clause[2],
|
||||||
|
clause[3]
|
||||||
|
);
|
||||||
|
if (negate) return vars.map(x => [0 - x]);
|
||||||
|
else return [vars];
|
||||||
|
} else {
|
||||||
|
const v = getVariable(JSON.stringify(clause), "");
|
||||||
|
if (negate) return [[0 - v]];
|
||||||
|
else return [[v]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cnf = recursive(exp, false);
|
||||||
|
|
||||||
|
for (const m of mutuallyExclusive.values()) {
|
||||||
|
const ar: number[] = Array.from(m);
|
||||||
|
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]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { vars: expressions.size, clauses: cnf };
|
||||||
|
}
|
||||||
+48
-114
@@ -17,7 +17,7 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import parsimmon from "parsimmon";
|
import * as parsimmon from "parsimmon";
|
||||||
import { Expression } from "../types";
|
import { Expression } from "../types";
|
||||||
|
|
||||||
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
|
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
|
||||||
@@ -27,7 +27,7 @@ function interpretEscapes(str): string {
|
|||||||
f: "\f",
|
f: "\f",
|
||||||
n: "\n",
|
n: "\n",
|
||||||
r: "\r",
|
r: "\r",
|
||||||
t: "\t",
|
t: "\t"
|
||||||
};
|
};
|
||||||
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
|
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
|
||||||
const type = escape.charAt(0);
|
const type = escape.charAt(0);
|
||||||
@@ -40,10 +40,7 @@ function interpretEscapes(str): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function map(
|
export function map(exp, callback): Expression {
|
||||||
exp: Expression,
|
|
||||||
callback: (exp: Expression) => Expression
|
|
||||||
): Expression {
|
|
||||||
if (!Array.isArray(exp)) return callback(exp);
|
if (!Array.isArray(exp)) return callback(exp);
|
||||||
|
|
||||||
let clone;
|
let clone;
|
||||||
@@ -58,10 +55,7 @@ export function map(
|
|||||||
return callback(clone || exp);
|
return callback(clone || exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mapAsync(
|
export async function mapAsync(exp, callback): Promise<Expression> {
|
||||||
exp: Expression,
|
|
||||||
callback: (exp: Expression) => Promise<Expression>
|
|
||||||
): Promise<Expression> {
|
|
||||||
if (!Array.isArray(exp)) return callback(exp);
|
if (!Array.isArray(exp)) return callback(exp);
|
||||||
|
|
||||||
let clone;
|
let clone;
|
||||||
@@ -76,10 +70,7 @@ export async function mapAsync(
|
|||||||
return callback(clone || exp);
|
return callback(clone || exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
function binaryLeft(
|
function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<{}> {
|
||||||
operatorsParser: parsimmon.Parser<string>,
|
|
||||||
nextParser: parsimmon.Parser<Expression>
|
|
||||||
): parsimmon.Parser<Expression> {
|
|
||||||
return parsimmon.seqMap(
|
return parsimmon.seqMap(
|
||||||
nextParser,
|
nextParser,
|
||||||
parsimmon.seq(operatorsParser, nextParser).many(),
|
parsimmon.seq(operatorsParser, nextParser).many(),
|
||||||
@@ -95,7 +86,7 @@ function binaryLeft(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lang = parsimmon.createLanguage({
|
const lang = parsimmon.createLanguage({
|
||||||
ComparisonOperator: function () {
|
ComparisonOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.alt(
|
.alt(
|
||||||
parsimmon.string(">="),
|
parsimmon.string(">="),
|
||||||
@@ -107,10 +98,13 @@ const lang = parsimmon.createLanguage({
|
|||||||
)
|
)
|
||||||
.skip(parsimmon.optWhitespace);
|
.skip(parsimmon.optWhitespace);
|
||||||
},
|
},
|
||||||
LikeOperator: function () {
|
LikeOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.alt(
|
.alt(
|
||||||
parsimmon.regexp(/like/i).result("LIKE").desc("LIKE"),
|
parsimmon
|
||||||
|
.regexp(/like/i)
|
||||||
|
.result("LIKE")
|
||||||
|
.desc("LIKE"),
|
||||||
parsimmon
|
parsimmon
|
||||||
.regexp(/not\s+like/i)
|
.regexp(/not\s+like/i)
|
||||||
.result("NOT LIKE")
|
.result("NOT LIKE")
|
||||||
@@ -119,7 +113,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
.skip(parsimmon.optWhitespace);
|
.skip(parsimmon.optWhitespace);
|
||||||
},
|
},
|
||||||
IsNullOperator: function () {
|
IsNullOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.alt(
|
.alt(
|
||||||
parsimmon
|
parsimmon
|
||||||
@@ -134,7 +128,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
.skip(parsimmon.optWhitespace);
|
.skip(parsimmon.optWhitespace);
|
||||||
},
|
},
|
||||||
NotOperator: function () {
|
NotOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/not/i)
|
.regexp(/not/i)
|
||||||
.result("NOT")
|
.result("NOT")
|
||||||
@@ -142,7 +136,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.desc("NOT");
|
.desc("NOT");
|
||||||
},
|
},
|
||||||
AndOperator: function () {
|
AndOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/and/i)
|
.regexp(/and/i)
|
||||||
.result("AND")
|
.result("AND")
|
||||||
@@ -150,7 +144,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.desc("AND");
|
.desc("AND");
|
||||||
},
|
},
|
||||||
OrOperator: function () {
|
OrOperator: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/or/i)
|
.regexp(/or/i)
|
||||||
.result("OR")
|
.result("OR")
|
||||||
@@ -158,7 +152,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.desc("OR");
|
.desc("OR");
|
||||||
},
|
},
|
||||||
Parameter: function (r) {
|
Parameter: function(r) {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.alt(
|
.alt(
|
||||||
parsimmon.regexp(/[a-zA-Z0-9_.*-]+/),
|
parsimmon.regexp(/[a-zA-Z0-9_.*-]+/),
|
||||||
@@ -168,43 +162,48 @@ const lang = parsimmon.createLanguage({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.atLeast(1)
|
.atLeast(1)
|
||||||
.map((x) => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]])
|
.map(x => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]])
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.desc("parameter");
|
.desc("parameter");
|
||||||
},
|
},
|
||||||
StringValueSql: function () {
|
StringValueSql: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/'([^']*)'/, 1)
|
.regexp(/'([^']*)'/, 1)
|
||||||
.atLeast(1)
|
.atLeast(1)
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.map((s) => s.join("'"))
|
.map(s => s.join("'"))
|
||||||
.desc("string");
|
.desc("string");
|
||||||
},
|
},
|
||||||
StringValueJs: function () {
|
StringValueJs: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/"((?:\\.|.)*?)"/, 1)
|
.regexp(/"((?:\\.|.)*?)"/, 1)
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.map(interpretEscapes)
|
.map(interpretEscapes)
|
||||||
.desc("string");
|
.desc("string");
|
||||||
},
|
},
|
||||||
NumberValue: function () {
|
NumberValue: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
|
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.map(Number)
|
.map(Number)
|
||||||
.desc("number");
|
.desc("number");
|
||||||
},
|
},
|
||||||
BooleanValue: function () {
|
BooleanValue: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.alt(
|
.alt(
|
||||||
parsimmon.regexp(/true/i).result(true).desc("TRUE"),
|
parsimmon
|
||||||
parsimmon.regexp(/false/i).result(false).desc("FALSE")
|
.regexp(/true/i)
|
||||||
|
.result(true)
|
||||||
|
.desc("TRUE"),
|
||||||
|
parsimmon
|
||||||
|
.regexp(/false/i)
|
||||||
|
.result(false)
|
||||||
|
.desc("FALSE")
|
||||||
)
|
)
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
.skip(parsimmon.optWhitespace);
|
.skip(parsimmon.optWhitespace);
|
||||||
},
|
},
|
||||||
NullValue: function () {
|
NullValue: function() {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/null/i)
|
.regexp(/null/i)
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
@@ -212,73 +211,32 @@ const lang = parsimmon.createLanguage({
|
|||||||
.result(null)
|
.result(null)
|
||||||
.desc("NULL");
|
.desc("NULL");
|
||||||
},
|
},
|
||||||
FuncValue: function (r) {
|
FuncValue: function(r) {
|
||||||
return parsimmon.seqMap(
|
return parsimmon.seqMap(
|
||||||
parsimmon
|
parsimmon
|
||||||
.regexp(/([a-zA-Z0-9_]+)/, 1)
|
.regexp(/([a-zA-Z0-9_]+)/, 1)
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.desc("function"),
|
.desc("function"),
|
||||||
r.ExpressionList.wrap(
|
r.ValueExpression.sepBy(
|
||||||
|
parsimmon.string(",").skip(parsimmon.optWhitespace)
|
||||||
|
).wrap(
|
||||||
parsimmon.string("(").skip(parsimmon.optWhitespace),
|
parsimmon.string("(").skip(parsimmon.optWhitespace),
|
||||||
parsimmon.string(")").skip(parsimmon.optWhitespace)
|
parsimmon.string(")").skip(parsimmon.optWhitespace)
|
||||||
),
|
),
|
||||||
(f, args) => ["FUNC", f.toUpperCase()].concat(args)
|
(f, args) => ["FUNC", f.toUpperCase()].concat(args)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
WhenPair: function (r) {
|
Value: function(r) {
|
||||||
return parsimmon.seq(
|
|
||||||
parsimmon
|
|
||||||
.regexp(/when/i)
|
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
.skip(parsimmon.optWhitespace)
|
|
||||||
.desc("WHEN")
|
|
||||||
.then(r.Expression),
|
|
||||||
parsimmon
|
|
||||||
.regexp(/then/i)
|
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
.skip(parsimmon.optWhitespace)
|
|
||||||
.desc("THEN")
|
|
||||||
.then(r.Expression)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
CaseStatement: function (r) {
|
|
||||||
return parsimmon.seqMap(
|
|
||||||
parsimmon
|
|
||||||
.regexp(/case/i)
|
|
||||||
.result("CASE")
|
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
.skip(parsimmon.optWhitespace)
|
|
||||||
.desc("CASE"),
|
|
||||||
r.WhenPair.many(),
|
|
||||||
parsimmon
|
|
||||||
.regexp(/else/i)
|
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
.skip(parsimmon.optWhitespace)
|
|
||||||
.desc("ELSE")
|
|
||||||
.then(r.Expression)
|
|
||||||
.map((e) => [[true, e]])
|
|
||||||
.fallback(null)
|
|
||||||
.skip(
|
|
||||||
parsimmon
|
|
||||||
.regex(/end/i)
|
|
||||||
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
|
||||||
)
|
|
||||||
.skip(parsimmon.optWhitespace),
|
|
||||||
(...arr) => arr.flat(2)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Value: function (r) {
|
|
||||||
return parsimmon.alt(
|
return parsimmon.alt(
|
||||||
r.NullValue,
|
r.NullValue,
|
||||||
r.BooleanValue,
|
r.BooleanValue,
|
||||||
r.NumberValue,
|
r.NumberValue,
|
||||||
r.StringValueSql,
|
r.StringValueSql,
|
||||||
r.StringValueJs,
|
r.StringValueJs,
|
||||||
r.FuncValue,
|
r.FuncValue
|
||||||
r.CaseStatement
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
ValueExpression: function (r) {
|
ValueExpression: function(r) {
|
||||||
return binaryLeft(
|
return binaryLeft(
|
||||||
parsimmon.string("||").skip(parsimmon.optWhitespace),
|
parsimmon.string("||").skip(parsimmon.optWhitespace),
|
||||||
binaryLeft(
|
binaryLeft(
|
||||||
@@ -301,7 +259,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Comparison: function (r) {
|
Comparison: function(r) {
|
||||||
return parsimmon.alt(
|
return parsimmon.alt(
|
||||||
parsimmon.seqMap(r.ValueExpression, r.IsNullOperator, (p, o) => [o, p]),
|
parsimmon.seqMap(r.ValueExpression, r.IsNullOperator, (p, o) => [o, p]),
|
||||||
parsimmon.seqMap(
|
parsimmon.seqMap(
|
||||||
@@ -331,16 +289,8 @@ const lang = parsimmon.createLanguage({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
ExpressionList: function (r) {
|
Expression: function(r) {
|
||||||
return r.Expression.sepBy(
|
function unary(operatorsParser, nextParser): parsimmon.Parser<{}> {
|
||||||
parsimmon.string(",").skip(parsimmon.optWhitespace)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Expression: function (r) {
|
|
||||||
function unary(
|
|
||||||
operatorsParser: parsimmon.Parser<string>,
|
|
||||||
nextParser: parsimmon.Parser<Expression>
|
|
||||||
): parsimmon.Parser<Expression> {
|
|
||||||
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
|
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +301,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
unary(r.NotOperator, r.Comparison.or(r.ValueExpression))
|
unary(r.NotOperator, r.Comparison.or(r.ValueExpression))
|
||||||
)
|
)
|
||||||
).trim(parsimmon.optWhitespace);
|
).trim(parsimmon.optWhitespace);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parse(str: string): Expression {
|
export function parse(str: string): Expression {
|
||||||
@@ -359,12 +309,7 @@ export function parse(str: string): Expression {
|
|||||||
return lang.Expression.tryParse(str);
|
return lang.Expression.tryParse(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseList(str: string): Expression[] {
|
export function stringify(exp, level = 0): string {
|
||||||
if (!str) return [];
|
|
||||||
return lang.ExpressionList.tryParse(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringify(exp: Expression, level = 0): string {
|
|
||||||
if (!Array.isArray(exp)) return JSON.stringify(exp);
|
if (!Array.isArray(exp)) return JSON.stringify(exp);
|
||||||
|
|
||||||
const opLevels = {
|
const opLevels = {
|
||||||
@@ -385,7 +330,7 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
"+": 31,
|
"+": 31,
|
||||||
"-": 31,
|
"-": 31,
|
||||||
"*": 32,
|
"*": 32,
|
||||||
"/": 32,
|
"/": 32
|
||||||
};
|
};
|
||||||
|
|
||||||
const op = exp[0].toUpperCase();
|
const op = exp[0].toUpperCase();
|
||||||
@@ -399,7 +344,7 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
return wrap(
|
return wrap(
|
||||||
`${exp[1]}(${exp
|
`${exp[1]}(${exp
|
||||||
.slice(2)
|
.slice(2)
|
||||||
.map((e) => stringify(e))
|
.map(e => stringify(e))
|
||||||
.join(", ")})`
|
.join(", ")})`
|
||||||
);
|
);
|
||||||
} else if (op === "PARAM") {
|
} else if (op === "PARAM") {
|
||||||
@@ -409,7 +354,7 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
return wrap(
|
return wrap(
|
||||||
exp[1]
|
exp[1]
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map((p) => {
|
.map(p => {
|
||||||
if (typeof p === "string") return p;
|
if (typeof p === "string") return p;
|
||||||
else return `{${stringify(p)}}`;
|
else return `{${stringify(p)}}`;
|
||||||
})
|
})
|
||||||
@@ -436,17 +381,6 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (op === "CASE") {
|
|
||||||
const parts: string[] = ["CASE"];
|
|
||||||
for (let i = 1; i < exp.length - 1; i += 2) {
|
|
||||||
if (!Array.isArray(exp[i]) && exp[i]) {
|
|
||||||
if (exp[i + 1] != null) parts.push("ELSE", stringify(exp[i + 1]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parts.push("WHEN", stringify(exp[i]), "THEN", stringify(exp[i + 1]));
|
|
||||||
}
|
|
||||||
parts.push("END");
|
|
||||||
return parts.join(" ");
|
|
||||||
} else if (op in opLevels) {
|
} else if (op in opLevels) {
|
||||||
const parts = exp.slice(1).map((e, i) => {
|
const parts = exp.slice(1).map((e, i) => {
|
||||||
return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0));
|
return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0));
|
||||||
@@ -459,7 +393,7 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseLikePattern(pat: string, esc: string): string[] {
|
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) {
|
||||||
@@ -474,5 +408,5 @@ export function parseLikePattern(pat: string, esc: string): string[] {
|
|||||||
while (chars[i + 1] === "%") chars[++i] = "";
|
while (chars[i + 1] === "%") chars[++i] = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chars.filter((c) => c);
|
return chars.filter(c => c);
|
||||||
}
|
}
|
||||||
+155
-225
@@ -18,11 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { map, mapAsync, parseLikePattern } from "./expression-parser";
|
import { map, mapAsync, parseLikePattern } from "./expression-parser";
|
||||||
|
import { booleanCnf } from "./expression-cnf";
|
||||||
|
import { naiveDpll } from "./sat-solver";
|
||||||
import { Expression } from "../types";
|
import { Expression } from "../types";
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
const regExpCache = new WeakMap<any, RegExp>();
|
const regExpCache = new WeakMap<object, RegExp>();
|
||||||
|
|
||||||
const REDUCE_SKIP = {};
|
const REDUCE_SKIP = {};
|
||||||
function reduce(exp, callback): Expression {
|
function reduce(exp, callback): Expression {
|
||||||
@@ -42,7 +44,7 @@ function reduce(exp, callback): Expression {
|
|||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function likePatternToRegExp(pat: string, esc = "", flags = ""): RegExp {
|
export function likePatternToRegExp(pat, esc = "", flags = ""): RegExp {
|
||||||
const convChars = {
|
const convChars = {
|
||||||
"-": "\\-",
|
"-": "\\-",
|
||||||
"/": "\\/",
|
"/": "\\/",
|
||||||
@@ -61,231 +63,159 @@ export function likePatternToRegExp(pat: string, esc = "", flags = ""): RegExp {
|
|||||||
"{": "\\{",
|
"{": "\\{",
|
||||||
"}": "\\}",
|
"}": "\\}",
|
||||||
"\\%": ".*",
|
"\\%": ".*",
|
||||||
"\\_": ".",
|
"\\_": "."
|
||||||
};
|
};
|
||||||
let chars = parseLikePattern(pat, esc);
|
let chars = parseLikePattern(pat, esc);
|
||||||
if (!chars.length) return new RegExp("^$", flags);
|
if (!chars.length) return new RegExp("^$", flags);
|
||||||
chars = chars.map((c) => convChars[c] || c);
|
chars = chars.map(c => convChars[c] || c);
|
||||||
chars[0] = chars[0] === ".*" ? "" : "^" + chars[0];
|
chars[0] = chars[0] === ".*" ? "" : "^" + chars[0];
|
||||||
const l = chars.length - 1;
|
const l = chars.length - 1;
|
||||||
chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$";
|
chars[l] = [".*", ""].includes(chars[l]) ? "" : chars[l] + "$";
|
||||||
return new RegExp(chars.join(""), flags);
|
return new RegExp(chars.join(""), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare(
|
function evalExp(e: Expression): Expression {
|
||||||
a: boolean | number | string,
|
if (e[0] === "AND") {
|
||||||
b: boolean | number | string
|
return reduce(e, (a, b) => {
|
||||||
): number {
|
if (!isArray(a)) return a ? b : a;
|
||||||
if (typeof a === "boolean") a = +a;
|
if (!isArray(b)) return b ? a : b;
|
||||||
if (typeof b === "boolean") b = +b;
|
return REDUCE_SKIP;
|
||||||
if (typeof a !== typeof b) return typeof a === "string" ? 1 : -1;
|
});
|
||||||
return a > b ? 1 : a < b ? -1 : 0;
|
} else if (e[0] === "OR") {
|
||||||
}
|
return reduce(e, (a, b) => {
|
||||||
|
if (!isArray(a)) return a ? a : b;
|
||||||
function toNumber(a: boolean | number | string): number {
|
if (!isArray(b)) return b ? b : a;
|
||||||
switch (typeof a) {
|
return REDUCE_SKIP;
|
||||||
case "number":
|
});
|
||||||
return a;
|
} else if (e[0] === "NOT") {
|
||||||
case "boolean":
|
if (!isArray(e[1])) return !e[1];
|
||||||
return +a;
|
else if (e[1][0] === "NOT") return e[1][1];
|
||||||
case "string":
|
} else if (e[0] === "IS NULL") {
|
||||||
return parseFloat(a) || 0;
|
if (isArray(e[1])) return e;
|
||||||
}
|
else if (e[1] == null) return true;
|
||||||
}
|
|
||||||
|
|
||||||
function toString(a: boolean | number | string): string {
|
|
||||||
switch (typeof a) {
|
|
||||||
case "string":
|
|
||||||
return a;
|
|
||||||
case "number":
|
|
||||||
return a.toString();
|
|
||||||
case "boolean":
|
|
||||||
return (+a).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function evaluateCallback(exp: Expression): Expression {
|
|
||||||
if (!Array.isArray(exp)) return exp;
|
|
||||||
if (exp[0] === "CASE") {
|
|
||||||
for (let i = 1; i < exp.length; i += 2) {
|
|
||||||
if (Array.isArray(exp[i])) return exp;
|
|
||||||
if (exp[i]) return exp[i + 1];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else if (exp[0] === "FUNC") {
|
|
||||||
if (exp[1] === "COALESCE") {
|
|
||||||
const args: Expression[] = [];
|
|
||||||
for (let i = 2; i < exp.length; ++i) {
|
|
||||||
const e = exp[i];
|
|
||||||
if (e == null) continue;
|
|
||||||
args.push(e);
|
|
||||||
if (!Array.isArray(e)) break;
|
|
||||||
}
|
|
||||||
if (!args.length) return null;
|
|
||||||
if (args.length === 1) return args[0];
|
|
||||||
return ["FUNC", "COALESCE", ...args];
|
|
||||||
} else if (exp[1] === "UPPER") {
|
|
||||||
if (exp[2] == null) return null;
|
|
||||||
if (!isArray(exp[2])) return toString(exp[2]).toUpperCase();
|
|
||||||
} else if (exp[1] === "LOWER") {
|
|
||||||
if (exp[2] == null) return null;
|
|
||||||
if (!isArray(exp[2])) return toString(exp[2]).toLowerCase();
|
|
||||||
}
|
|
||||||
} else if (exp[0] === "PARAM") {
|
|
||||||
if (exp[1] == null) return null;
|
|
||||||
} else if (exp[0] === "AND") {
|
|
||||||
for (let i = 1; i < exp.length; ++i)
|
|
||||||
if (!Array.isArray(exp[i]) && exp[i] != null && !exp[i]) return false;
|
|
||||||
const args: Expression[] = [];
|
|
||||||
for (let i = 1; i < exp.length; ++i) {
|
|
||||||
const ee = exp[i];
|
|
||||||
if (ee == null) return null;
|
|
||||||
if (Array.isArray(ee)) {
|
|
||||||
if (ee[0] === "AND") args.push(...ee.slice(1));
|
|
||||||
else args.push(ee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!args.length) return true;
|
|
||||||
if (args.length === 1) args.push(true);
|
|
||||||
return ["AND", ...args];
|
|
||||||
} else if (exp[0] === "OR") {
|
|
||||||
const args: Expression[] = [];
|
|
||||||
for (let i = 1; i < exp.length; ++i) {
|
|
||||||
const ee = exp[i];
|
|
||||||
if (Array.isArray(ee)) {
|
|
||||||
if (ee[0] === "OR") args.push(...ee.slice(1));
|
|
||||||
else args.push(ee);
|
|
||||||
} else if (ee) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!args.length) return exp.some((ee) => ee == null) ? null : false;
|
|
||||||
if (args.length === 1) args.push(false);
|
|
||||||
return ["OR", ...args];
|
|
||||||
} else if (exp[0] === "NOT") {
|
|
||||||
if (exp[1] == null) return null;
|
|
||||||
if (!isArray(exp[1])) return !exp[1];
|
|
||||||
else if (exp[1][0] === "NOT") return exp[1][1];
|
|
||||||
} else if (exp[0] === "IS NULL") {
|
|
||||||
if (isArray(exp[1])) return exp;
|
|
||||||
else if (exp[1] == null) return true;
|
|
||||||
else return null;
|
else return null;
|
||||||
} else if (exp[0] === "IS NOT NULL") {
|
} else if (e[0] === "IS NOT NULL") {
|
||||||
if (isArray(exp[1])) return exp;
|
if (isArray(e[1])) return e;
|
||||||
else if (exp[1] != null) return true;
|
else if (e[1] != null) return true;
|
||||||
else return null;
|
else return null;
|
||||||
} else if (exp[0] === "LIKE") {
|
} else if (e[0] === "LIKE") {
|
||||||
if (isArray(exp[1]) || isArray(exp[2]) || isArray(exp[3])) return exp;
|
if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e;
|
||||||
else if (
|
else if (
|
||||||
exp[1] == null ||
|
e[1] == null ||
|
||||||
exp[2] == null ||
|
e[2] == null ||
|
||||||
((exp as any[]).length >= 4 && exp[3] == null)
|
((e as any[]).length >= 4 && e[3] == null)
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
let r = regExpCache.get(exp as any[]);
|
let r = regExpCache.get(e as any[]);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
r = likePatternToRegExp(exp[2], exp[3]);
|
r = likePatternToRegExp(e[2], e[3]);
|
||||||
regExpCache.set(exp as any[], r);
|
regExpCache.set(e as any[], r);
|
||||||
}
|
}
|
||||||
return r.test(exp[1]);
|
return r.test(e[1]);
|
||||||
} else if (exp[0] === "NOT LIKE") {
|
} else if (e[0] === "NOT LIKE") {
|
||||||
if (isArray(exp[1]) || isArray(exp[2]) || isArray(exp[3])) return exp;
|
if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e;
|
||||||
else if (
|
else if (
|
||||||
exp[1] == null ||
|
e[1] == null ||
|
||||||
exp[2] == null ||
|
e[2] == null ||
|
||||||
((exp as any[]).length >= 4 && exp[3] == null)
|
((e as any[]).length >= 4 && e[3] == null)
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
let r = regExpCache.get(exp as any[]);
|
let r = regExpCache.get(e as any[]);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
r = likePatternToRegExp(exp[2], exp[3]);
|
r = likePatternToRegExp(e[2], e[3]);
|
||||||
regExpCache.set(exp as any[], r);
|
regExpCache.set(e as any[], r);
|
||||||
}
|
}
|
||||||
return !r.test(exp[1]);
|
return !r.test(e[1]);
|
||||||
} else if (exp[0] === "=") {
|
} else if (e[0] === "=") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) === 0;
|
return e[1] === e[2];
|
||||||
} else if (exp[0] === "<>") {
|
} else if (e[0] === "<>") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) !== 0;
|
return e[1] !== e[2];
|
||||||
} else if (exp[0] === ">") {
|
} else if (e[0] === ">") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) > 0;
|
return e[1] > e[2];
|
||||||
} else if (exp[0] === ">=") {
|
} else if (e[0] === ">=") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) >= 0;
|
return e[1] >= e[2];
|
||||||
} else if (exp[0] === "<") {
|
} else if (e[0] === "<") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) < 0;
|
return e[1] < e[2];
|
||||||
} else if (exp[0] === "<=") {
|
} else if (e[0] === "<=") {
|
||||||
if (exp[1] == null || exp[2] == null) return null;
|
if (isArray(e[1]) || isArray(e[2])) return e;
|
||||||
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
if (e[1] == null || e[2] == null) return null;
|
||||||
return compare(exp[1], exp[2]) <= 0;
|
return e[1] <= e[2];
|
||||||
} else if (exp[0] === "*") {
|
} else if (e[0] === "*") {
|
||||||
return reduce(exp, (a, b) => {
|
return reduce(e, (a, b) => {
|
||||||
if (a == null || b == null) return null;
|
if (!isArray(a) && !isArray(b)) {
|
||||||
if (!isArray(a) && !isArray(b)) return toNumber(a) * toNumber(b);
|
if (a == null || b == null) return null;
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (exp[0] === "/") {
|
} else if (e[0] === "/") {
|
||||||
return reduce(exp, (a, b, i) => {
|
return reduce(e, (a, b, i) => {
|
||||||
if (a == null || b == null) return null;
|
if (!isArray(a) && !isArray(b)) {
|
||||||
if (!isArray(a) && !isArray(b))
|
if (a == null || b == null) return null;
|
||||||
return i === 0 ? toNumber(a) / toNumber(b) : toNumber(a) * toNumber(b);
|
return i === 0 ? a / b : a * b;
|
||||||
|
}
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (exp[0] === "+") {
|
} else if (e[0] === "+") {
|
||||||
return reduce(exp, (a, b) => {
|
return reduce(e, (a, b) => {
|
||||||
if (a == null || b == null) return null;
|
if (!isArray(a) && !isArray(b)) {
|
||||||
if (!isArray(a) && !isArray(b)) return toNumber(a) + toNumber(b);
|
if (a == null || b == null) return null;
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (exp[0] === "-") {
|
} else if (e[0] === "-") {
|
||||||
return reduce(exp, (a, b, i) => {
|
return reduce(e, (a, b, i) => {
|
||||||
if (a == null || b == null) return null;
|
if (!isArray(a) && !isArray(b)) {
|
||||||
if (!isArray(a) && !isArray(b))
|
if (a == null || b == null) return null;
|
||||||
return i === 0 ? toNumber(a) - toNumber(b) : toNumber(a) + toNumber(b);
|
return i === 0 ? a - b : a + b;
|
||||||
|
}
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (exp[0] === "||") {
|
} else if (e[0] === "||") {
|
||||||
return reduce(exp, (a, b) => {
|
return reduce(e, (a, b) => {
|
||||||
if (a == null || b == null) return null;
|
if (!isArray(a) && !isArray(b)) {
|
||||||
if (!isArray(a) && !isArray(b)) return toString(a) + toString(b);
|
if (a == null || b == null) return null;
|
||||||
|
return `${a}${b}`;
|
||||||
|
}
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return exp;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evaluate(
|
export function evaluate(
|
||||||
exp: Expression,
|
exp,
|
||||||
obj: Record<string, unknown> | ((exp: string) => Expression),
|
obj,
|
||||||
now: number,
|
now: number,
|
||||||
cb?: (exp: Expression) => Expression
|
cb?: Function
|
||||||
): string | number | boolean | null;
|
): string | number | boolean | null;
|
||||||
export function evaluate(
|
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression;
|
||||||
exp: Expression,
|
export function evaluate(exp, obj?, now?: number, cb?: Function): Expression {
|
||||||
obj?: Record<string, unknown> | ((exp: string) => Expression),
|
return map(exp, e => {
|
||||||
now?: number,
|
|
||||||
cb?: (exp: Expression) => Expression
|
|
||||||
): Expression;
|
|
||||||
export function evaluate(
|
|
||||||
exp: Expression,
|
|
||||||
obj?: Record<string, unknown> | ((exp: string) => Expression),
|
|
||||||
now?: number,
|
|
||||||
cb?: (exp: Expression) => Expression
|
|
||||||
): Expression {
|
|
||||||
return map(exp, (e) => {
|
|
||||||
if (cb) e = cb(e);
|
if (cb) e = cb(e);
|
||||||
if (!isArray(e)) return e;
|
if (!isArray(e)) return e;
|
||||||
|
|
||||||
if (e[0] === "FUNC" && e[1] === "NOW") {
|
if (e[0] === "FUNC") {
|
||||||
if (now) return now;
|
if (e[1] === "NOW") {
|
||||||
|
if (now) return now;
|
||||||
|
} else if (e[1] === "UPPER") {
|
||||||
|
if (e[2] == null) return null;
|
||||||
|
if (!isArray(e[2])) return `${e[2]}`.toUpperCase();
|
||||||
|
} else if (e[1] === "LOWER") {
|
||||||
|
if (e[2] == null) return null;
|
||||||
|
if (!isArray(e[2])) return `${e[2]}`.toLowerCase();
|
||||||
|
}
|
||||||
} else if (e[0] === "PARAM") {
|
} else if (e[0] === "PARAM") {
|
||||||
if (e[1] == null) return null;
|
if (e[1] == null) return null;
|
||||||
if (obj && !isArray(e[1])) {
|
if (obj && !isArray(e[1])) {
|
||||||
@@ -298,29 +228,29 @@ export function evaluate(
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evaluateCallback(e);
|
return evalExp(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function evaluateAsync(
|
export async function evaluateAsync(
|
||||||
exp: Expression,
|
exp,
|
||||||
obj: Record<string, unknown>,
|
obj,
|
||||||
now: number,
|
now: number,
|
||||||
cb?: (exp: Expression) => Promise<Expression>
|
cb?: Function
|
||||||
): Promise<string | number | boolean | null>;
|
): Promise<string | number | boolean | null>;
|
||||||
export async function evaluateAsync(
|
export async function evaluateAsync(
|
||||||
exp: Expression,
|
exp,
|
||||||
obj?: Record<string, unknown>,
|
obj?,
|
||||||
now?: number,
|
now?: number,
|
||||||
cb?: (exp: Expression) => Promise<Expression>
|
cb?: Function
|
||||||
): Promise<Expression>;
|
): Promise<Expression>;
|
||||||
export async function evaluateAsync(
|
export async function evaluateAsync(
|
||||||
exp: Expression,
|
exp,
|
||||||
obj?: Record<string, unknown>,
|
obj?,
|
||||||
now?: number,
|
now?: number,
|
||||||
cb?: (exp: Expression) => Promise<Expression>
|
cb?: Function
|
||||||
): Promise<Expression> {
|
): Promise<Expression> {
|
||||||
return mapAsync(exp, async (e) => {
|
return mapAsync(exp, async e => {
|
||||||
if (cb) e = await cb(e);
|
if (cb) e = await cb(e);
|
||||||
if (!isArray(e)) return e;
|
if (!isArray(e)) return e;
|
||||||
|
|
||||||
@@ -339,59 +269,59 @@ export async function evaluateAsync(
|
|||||||
if (obj && !isArray(e[1])) {
|
if (obj && !isArray(e[1])) {
|
||||||
let v = obj[e[1]];
|
let v = obj[e[1]];
|
||||||
if (v == null) return null;
|
if (v == null) return null;
|
||||||
if (typeof v === "object") v = v["value"] ? v["value"][0] : null;
|
if (typeof v === "object") v = v.value ? v.value[0] : null;
|
||||||
return v as Expression;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evaluateCallback(e);
|
return evalExp(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function and(exp1: Expression, exp2: Expression): Expression {
|
export function and(exp1: Expression, exp2: Expression): Expression {
|
||||||
if (exp1 != null && !exp1) return false;
|
if (!isArray(exp1)) return exp1 ? exp2 : exp1;
|
||||||
if (exp2 != null && !exp2) return false;
|
if (!isArray(exp2)) return exp2 ? exp1 : exp2;
|
||||||
if (!Array.isArray(exp1) && !Array.isArray(exp2)) {
|
|
||||||
if (exp1 == null || exp2 == null) return null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(exp2) && exp1[0] === "AND") return exp1;
|
|
||||||
if (!Array.isArray(exp1) && exp2[0] === "AND") return exp2;
|
|
||||||
|
|
||||||
const res: Expression = ["AND"];
|
let res: Expression = ["AND"];
|
||||||
|
|
||||||
if (Array.isArray(exp1) && exp1[0] === "AND") res.push(...exp1.slice(1));
|
if (exp1[0] === "AND") res = res.concat(exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
|
|
||||||
if (Array.isArray(exp2) && exp2[0] === "AND") res.push(...exp2.slice(1));
|
if (exp2[0] === "AND") res = res.concat(exp2.slice(1));
|
||||||
else res.push(exp2);
|
else res.push(exp2);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function or(exp1: Expression, exp2: Expression): Expression {
|
export function or(exp1: Expression, exp2: Expression): Expression {
|
||||||
if (!Array.isArray(exp1) && exp1) return true;
|
if (!isArray(exp1)) return exp1 ? exp1 : exp2;
|
||||||
if (!Array.isArray(exp2) && exp2) return true;
|
if (!isArray(exp2)) return exp2 ? exp2 : exp1;
|
||||||
if (!Array.isArray(exp1) && !Array.isArray(exp2)) {
|
|
||||||
if (exp1 == null || exp2 == null) return null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(exp2) && exp1[0] === "OR") return exp1;
|
|
||||||
if (!Array.isArray(exp1) && exp2[0] === "OR") return exp2;
|
|
||||||
|
|
||||||
const res: Expression = ["OR"];
|
let res: Expression = ["OR"];
|
||||||
|
|
||||||
if (Array.isArray(exp1) && exp1[0] === "OR") res.push(...exp1.slice(1));
|
if (exp1[0] === "OR") res = res.concat(exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
|
|
||||||
if (Array.isArray(exp2) && exp2[0] === "OR") res.push(...exp2.slice(1));
|
if (exp2[0] === "OR") res = res.concat(exp2.slice(1));
|
||||||
else res.push(exp2);
|
else res.push(exp2);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractParams(exp: Expression): Expression[] {
|
export function not(exp): Expression {
|
||||||
|
if (isArray(exp) && exp[0] === "NOT") return exp[1];
|
||||||
|
return ["NOT", exp];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subset(exp1, exp2): boolean {
|
||||||
|
const e = evaluate(["NOT", ["OR", ["NOT", exp1], exp2]]);
|
||||||
|
if (!isArray(e)) return !e;
|
||||||
|
const { vars, clauses } = booleanCnf(e);
|
||||||
|
return !naiveDpll(clauses, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractParams(exp): Expression[] {
|
||||||
const params = new Set<Expression>();
|
const params = new Set<Expression>();
|
||||||
map(exp, (e) => {
|
map(exp, e => {
|
||||||
if (isArray(e) && e[0] === "PARAM") params.add(e[1]);
|
if (isArray(e) && e[0] === "PARAM") params.add(e[1]);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function getKey(obj): string {
|
|||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function memoize<T extends (...args: any[]) => any>(func: T): T {
|
export default function memoize<T extends Function>(func: T): T {
|
||||||
const funcKey = getKey(func);
|
const funcKey = getKey(func);
|
||||||
return ((...args) => {
|
return ((...args) => {
|
||||||
const key = JSON.stringify(args.map(getKey)) + funcKey;
|
const key = JSON.stringify(args.map(getKey)) + funcKey;
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export default class PathSet {
|
|||||||
|
|
||||||
public find(
|
public find(
|
||||||
path: Path,
|
path: Path,
|
||||||
superset = false,
|
superset: boolean = false,
|
||||||
subset = false,
|
subset: boolean = false,
|
||||||
depth: number = path.length
|
depth: number = path.length
|
||||||
): Path[] {
|
): Path[] {
|
||||||
if (path.alias) throw new Error("PathSet does not support aliased paths");
|
if (path.alias) throw new Error("PathSet does not support aliased paths");
|
||||||
@@ -106,24 +106,24 @@ export default class PathSet {
|
|||||||
if (!fragmentIndexSet1) {
|
if (!fragmentIndexSet1) {
|
||||||
if (!fragmentIndexSet2) return [];
|
if (!fragmentIndexSet2) return [];
|
||||||
if (!res) res = [...fragmentIndexSet2];
|
if (!res) res = [...fragmentIndexSet2];
|
||||||
else res = res.filter((r) => fragmentIndexSet2.has(r));
|
else res = res.filter(r => fragmentIndexSet2.has(r));
|
||||||
} else if (!fragmentIndexSet2) {
|
} else if (!fragmentIndexSet2) {
|
||||||
if (!res) res = [...fragmentIndexSet1];
|
if (!res) res = [...fragmentIndexSet1];
|
||||||
else res = res.filter((r) => fragmentIndexSet1.has(r));
|
else res = res.filter(r => fragmentIndexSet1.has(r));
|
||||||
} else {
|
} else {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
res = [...fragmentIndexSet1, ...fragmentIndexSet2];
|
res = [...fragmentIndexSet1, ...fragmentIndexSet2];
|
||||||
} else {
|
} else {
|
||||||
res = res.filter(
|
res = res.filter(
|
||||||
(r) => fragmentIndexSet1.has(r) || fragmentIndexSet2.has(r)
|
r => fragmentIndexSet1.has(r) || fragmentIndexSet2.has(r)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!res.length) return res;
|
if (!res.length) return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res) res = [].concat(...lengthIndex.map((a) => [...a]));
|
if (!res) res = [].concat(...lengthIndex.map(a => [...a]));
|
||||||
else res = res.filter((r) => lengthIndex.some((a) => a.has(r)));
|
else res = res.filter(r => lengthIndex.some(a => a.has(r)));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -97,7 +97,6 @@ export default class Path {
|
|||||||
)
|
)
|
||||||
++index;
|
++index;
|
||||||
const s = pattern.slice(j, index).trim();
|
const s = pattern.slice(j, index).trim();
|
||||||
if (!s) throw new Error("Invalid parameter path");
|
|
||||||
segments.push(s);
|
segments.push(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ export default class Path {
|
|||||||
if (Array.isArray(s)) {
|
if (Array.isArray(s)) {
|
||||||
alias |= 1 << i;
|
alias |= 1 << i;
|
||||||
const parts = s.map(
|
const parts = s.map(
|
||||||
(al) => `${al[0].toString()}:${JSON.stringify(al[1])}`
|
al => `${al[0].toString()}:${JSON.stringify(al[1])}`
|
||||||
);
|
);
|
||||||
return `[${parts.join(",")}]`;
|
return `[${parts.join(",")}]`;
|
||||||
} else if (s === "*") {
|
} else if (s === "*") {
|
||||||
@@ -160,7 +159,7 @@ export default class Path {
|
|||||||
return this._string;
|
return this._string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public slice(start = 0, end: number = this.segments.length): Path {
|
public slice(start: number = 0, end: number = this.segments.length): Path {
|
||||||
if (start < 0) start = Math.max(0, this.segments.length + start);
|
if (start < 0) start = Math.max(0, this.segments.length + start);
|
||||||
if (end < 0) end = Math.max(0, this.segments.length + end);
|
if (end < 0) end = Math.max(0, this.segments.length + end);
|
||||||
|
|
||||||
@@ -212,7 +211,7 @@ export default class Path {
|
|||||||
|
|
||||||
public stripAlias(): Path {
|
public stripAlias(): Path {
|
||||||
if (!this.alias) return this;
|
if (!this.alias) return this;
|
||||||
const segments = this.segments.map((s) => (Array.isArray(s) ? "*" : s));
|
const segments = this.segments.map(s => (Array.isArray(s) ? "*" : s));
|
||||||
const str = segments.join(".");
|
const str = segments.join(".");
|
||||||
|
|
||||||
let path = cache1.get(str);
|
let path = cache1.get(str);
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* Naive DPLL solver for propositional logic adapted from
|
||||||
|
* https://github.com/tammet/logictools
|
||||||
|
*
|
||||||
|
* Origianl code by Tanel Tammet released under the MIT license.
|
||||||
|
*
|
||||||
|
* Check whether a clause set is satisfiable or contradictory, using a naive
|
||||||
|
* implementation of the dpll (Davis-Putnam-Logemann-Loveland) procedure
|
||||||
|
* combining truth table search with the resolution-like unit propagation.
|
||||||
|
*
|
||||||
|
* The standard pure-literal rule is not implemented in this naive version,
|
||||||
|
* since it is not really crucial for the algorithm.
|
||||||
|
*
|
||||||
|
* The code is very similar to the pure table solver, except we use a
|
||||||
|
* unit-propagating, derived-varlist-returing satisfiableAt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
|
|
||||||
|
function satisfiableAt(clauses, varVals, varnr, val): boolean {
|
||||||
|
if (varnr !== 0) varVals[varnr] = val;
|
||||||
|
|
||||||
|
const propRes = unitPropagate(clauses, varVals);
|
||||||
|
if (propRes === 1) {
|
||||||
|
varVals[varnr] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if result false (-1) unitPropagate restores the varVals state itself
|
||||||
|
if (propRes === -1) {
|
||||||
|
varVals[varnr] = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find next unassigned var to split
|
||||||
|
let nextVar = 0;
|
||||||
|
for (let i = 1; i < varVals.length; i++) {
|
||||||
|
if (varVals[i] === 0) {
|
||||||
|
nextVar = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextVar === 0) throw new Error("Error in satisfiableAt");
|
||||||
|
|
||||||
|
if (
|
||||||
|
satisfiableAt(clauses, varVals, nextVar, 1) ||
|
||||||
|
satisfiableAt(clauses, varVals, nextVar, -1)
|
||||||
|
)
|
||||||
|
// not necessary to restore split var: search is halted anyway
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// restore split var
|
||||||
|
varVals[nextVar] = 0;
|
||||||
|
|
||||||
|
// restore derived vars
|
||||||
|
for (let i = 0; i < propRes.length; i++) varVals[propRes[i]] = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether clauses are satisfiable under the varVals assignment, without
|
||||||
|
* search.
|
||||||
|
*
|
||||||
|
* Iterate through all clauses and check whether
|
||||||
|
*
|
||||||
|
* - all literals in the clause are assigned false: undo derived units (set
|
||||||
|
* values in varVals to 0) and return false immediately
|
||||||
|
*
|
||||||
|
* - a clause contains a literal assigned true: skip further actions on this
|
||||||
|
* clause
|
||||||
|
*
|
||||||
|
* - a single literal in the clause is unassigned, all the rest are assigned
|
||||||
|
* false: derive this literal as a unit clause.
|
||||||
|
*
|
||||||
|
* In case at least one literal is derived during the process above, iterate
|
||||||
|
* the whole process until no more new literals are derived.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
*
|
||||||
|
* 1: all clauses were assigned true, hence the clause set is satisfiable.
|
||||||
|
* The whole search will terminate after that.
|
||||||
|
*
|
||||||
|
* -1: clause set is contradictory. Derived units have been restored.
|
||||||
|
*
|
||||||
|
* a list of derived variables: the status of the clause set is
|
||||||
|
* undetermined. The values of variables on the list will be restored (set to
|
||||||
|
* 0) during backtracking.
|
||||||
|
*/
|
||||||
|
function unitPropagate(clauses, varVals): number[] | 1 | -1 {
|
||||||
|
// all vars with a value derived in the forthcoming main loop
|
||||||
|
const derivedVars = [];
|
||||||
|
|
||||||
|
// the main loop is run, deriving new unit clauses, until either:
|
||||||
|
// - the clause set is detected to be unsatisfiable
|
||||||
|
// - the clause set is detected to be satisfiable
|
||||||
|
// - there are no more derived unit clauses
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
let allClausesFound = true; // set to false if we find an undetermined clause
|
||||||
|
const derivedQueue = []; // derived units (literals) during one iteration
|
||||||
|
for (let i = 0; i < clauses.length; i++) {
|
||||||
|
const clause = clauses[i];
|
||||||
|
let clauseVal = 0;
|
||||||
|
let unassignedCount = 0; // count unassigned vars in clause
|
||||||
|
let unassignedLit = 0; // 0 means none found for this clause
|
||||||
|
|
||||||
|
for (let j = 0; j < clause.length; j++) {
|
||||||
|
const lit = clause[j];
|
||||||
|
let nr, polarity;
|
||||||
|
if (lit < 0) {
|
||||||
|
nr = 0 - lit;
|
||||||
|
polarity = -1;
|
||||||
|
} else {
|
||||||
|
nr = lit;
|
||||||
|
polarity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varVals[nr] === polarity) {
|
||||||
|
clauseVal = 1;
|
||||||
|
break;
|
||||||
|
} else if (varVals[nr] === 0) {
|
||||||
|
unassignedCount++;
|
||||||
|
unassignedLit = lit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clauseVal === 1) continue; // clause is subsumed
|
||||||
|
|
||||||
|
// clause is not subsumed by varVals
|
||||||
|
if (unassignedCount === 0) {
|
||||||
|
for (let j = 0; j < derivedVars.length; j++)
|
||||||
|
varVals[derivedVars[j]] = 0; // restore derived
|
||||||
|
return -1;
|
||||||
|
} else if (unassignedCount === 1) {
|
||||||
|
// unassignedLit is a derived literal
|
||||||
|
derivedQueue.push(unassignedLit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unassignedCount !== 0) allClausesFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if all clauses were subsumed or all the variables in a clause had a value
|
||||||
|
// no need to restore varVals: whole search finished
|
||||||
|
if (allClausesFound) return 1;
|
||||||
|
|
||||||
|
// in case there are any derived units present, store to varVals and
|
||||||
|
// iterate the main loop
|
||||||
|
if (derivedQueue.length === 0) break;
|
||||||
|
|
||||||
|
// some units were derived: iterate the main loop
|
||||||
|
for (let j = 0; j < derivedQueue.length; j++) {
|
||||||
|
const lit = derivedQueue[j];
|
||||||
|
let nr, polarity;
|
||||||
|
if (lit < 0) {
|
||||||
|
nr = 0 - lit;
|
||||||
|
polarity = -1;
|
||||||
|
} else {
|
||||||
|
nr = lit;
|
||||||
|
polarity = 1;
|
||||||
|
}
|
||||||
|
varVals[nr] = polarity;
|
||||||
|
derivedVars.push(nr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return derivedVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function naiveDpll(clauses, maxvarnr): boolean {
|
||||||
|
// variable values are 0 if not set, 1 if positive, -1 if negative
|
||||||
|
const varVals = new Int32Array(maxvarnr + 1);
|
||||||
|
return satisfiableAt(clauses, varVals, 0, 0);
|
||||||
|
}
|
||||||
+52
-27
@@ -32,13 +32,13 @@ while (!existsSync(`${ROOT_DIR}/package.json`)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For compatibility with v1.1
|
// For compatibility with v1.1
|
||||||
let configDir, cwmpSsl, nbiSsl, fsSsl, uiSsl, fsHostname;
|
let configDir, cwmpSsl, nbiSsl, fsSsl, uiSsl;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
EXT_DIR: { type: "path", default: resolve(ROOT_DIR, "config/ext") },
|
EXT_DIR: { type: "path", default: resolve(ROOT_DIR, "config/ext") },
|
||||||
MONGODB_CONNECTION_URL: {
|
MONGODB_CONNECTION_URL: {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: "mongodb://127.0.0.1/genieacs",
|
default: "mongodb://127.0.0.1/genieacs"
|
||||||
},
|
},
|
||||||
|
|
||||||
CWMP_WORKER_PROCESSES: { type: "int", default: 0 },
|
CWMP_WORKER_PROCESSES: { type: "int", default: 0 },
|
||||||
@@ -62,7 +62,7 @@ const options = {
|
|||||||
FS_INTERFACE: { type: "string", default: "0.0.0.0" },
|
FS_INTERFACE: { type: "string", default: "0.0.0.0" },
|
||||||
FS_SSL_CERT: { type: "string", default: "" },
|
FS_SSL_CERT: { type: "string", default: "" },
|
||||||
FS_SSL_KEY: { type: "string", default: "" },
|
FS_SSL_KEY: { type: "string", default: "" },
|
||||||
FS_URL_PREFIX: { type: "string", default: "" },
|
FS_HOSTNAME: { type: "string", default: "" },
|
||||||
FS_LOG_FILE: { type: "path", default: "" },
|
FS_LOG_FILE: { type: "path", default: "" },
|
||||||
FS_ACCESS_LOG_FILE: { type: "path", default: "" },
|
FS_ACCESS_LOG_FILE: { type: "path", default: "" },
|
||||||
|
|
||||||
@@ -76,7 +76,6 @@ const options = {
|
|||||||
UI_JWT_SECRET: { type: "string", default: "" },
|
UI_JWT_SECRET: { type: "string", default: "" },
|
||||||
|
|
||||||
UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 },
|
UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 },
|
||||||
FORWARDED_HEADER: { type: "string", default: "" },
|
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT: { type: "int", default: 3600 },
|
DOWNLOAD_TIMEOUT: { type: "int", default: 3600 },
|
||||||
EXT_TIMEOUT: { type: "int", default: 3000 },
|
EXT_TIMEOUT: { type: "int", default: 3000 },
|
||||||
@@ -99,8 +98,16 @@ const options = {
|
|||||||
CONNECTION_REQUEST_ALLOW_BASIC_AUTH: { type: "bool", default: false },
|
CONNECTION_REQUEST_ALLOW_BASIC_AUTH: { type: "bool", default: false },
|
||||||
MAX_COMMIT_ITERATIONS: { type: "int", default: 32 },
|
MAX_COMMIT_ITERATIONS: { type: "int", default: 32 },
|
||||||
|
|
||||||
|
// XML configuration
|
||||||
|
XML_RECOVER: { type: "bool", default: false },
|
||||||
|
XML_IGNORE_ENC: { type: "bool", default: false },
|
||||||
|
XML_FORMAT: { type: "bool", default: false },
|
||||||
|
XML_NO_DECL: { type: "bool", default: false },
|
||||||
|
XML_NO_EMPTY: { type: "bool", default: false },
|
||||||
|
XML_LIBXMLJS: { type: "bool", default: false },
|
||||||
|
|
||||||
// Should probably never be changed
|
// Should probably never be changed
|
||||||
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 },
|
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 }
|
||||||
};
|
};
|
||||||
|
|
||||||
const allConfig: { [name: string]: string | number } = {};
|
const allConfig: { [name: string]: string | number } = {};
|
||||||
@@ -112,20 +119,37 @@ function setConfig(name, value, commandLineArgument = false): boolean {
|
|||||||
if (name === "CONFIG_DIR" || name === "config-dir")
|
if (name === "CONFIG_DIR" || name === "config-dir")
|
||||||
configDir = configDir || resolve(ROOT_DIR, value);
|
configDir = configDir || resolve(ROOT_DIR, value);
|
||||||
|
|
||||||
if (name === "CWMP_SSL" || name === "cwmp-ssl")
|
if (name === "CWMP_SSL" || name === "cwmp-ssl") {
|
||||||
cwmpSsl = cwmpSsl || String(value).toLowerCase().trim();
|
cwmpSsl =
|
||||||
|
cwmpSsl ||
|
||||||
|
String(value)
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (name === "NBI_SSL" || name === "nbi-ssl")
|
if (name === "NBI_SSL" || name === "nbi-ssl") {
|
||||||
nbiSsl = nbiSsl || String(value).toLowerCase().trim();
|
nbiSsl =
|
||||||
|
nbiSsl ||
|
||||||
|
String(value)
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (name === "FS_SSL" || name === "fs-ssl")
|
if (name === "FS_SSL" || name === "fs-ssl") {
|
||||||
fsSsl = fsSsl || String(value).toLowerCase().trim();
|
fsSsl =
|
||||||
|
fsSsl ||
|
||||||
|
String(value)
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (name === "UI_SSL" || name === "ui-ssl")
|
if (name === "UI_SSL" || name === "ui-ssl") {
|
||||||
uiSsl = uiSsl || String(value).toLowerCase().trim();
|
uiSsl =
|
||||||
|
uiSsl ||
|
||||||
if (name === "FS_HOSTNAME" || name === "fs-hostname")
|
String(value)
|
||||||
fsHostname = fsHostname || String(value).trim();
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
// For compatibility with v1.0
|
// For compatibility with v1.0
|
||||||
if (name === "PRESETS_CACHE_DURATION" || name === "presets-cache-duration")
|
if (name === "PRESETS_CACHE_DURATION" || name === "presets-cache-duration")
|
||||||
@@ -143,6 +167,12 @@ function setConfig(name, value, commandLineArgument = false): boolean {
|
|||||||
)
|
)
|
||||||
setConfig("GPV_BATCH_SIZE", value);
|
setConfig("GPV_BATCH_SIZE", value);
|
||||||
|
|
||||||
|
if (name === "XML_PARSE_IGNORE_ENC" || name === "xml-parse-ignore-enc")
|
||||||
|
setConfig("XML_IGNORE_ENC", value);
|
||||||
|
|
||||||
|
if (name === "XML_PARSE_RECOVER" || name === "xml-parse-recover")
|
||||||
|
setConfig("XML_RECOVER", value);
|
||||||
|
|
||||||
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): string | number | boolean {
|
function cast(val, type): string | number | boolean {
|
||||||
@@ -150,7 +180,11 @@ function setConfig(name, value, commandLineArgument = false): boolean {
|
|||||||
case "int":
|
case "int":
|
||||||
return Number(val);
|
return Number(val);
|
||||||
case "bool":
|
case "bool":
|
||||||
return ["true", "1"].includes(String(val).trim().toLowerCase());
|
return ["true", "1"].includes(
|
||||||
|
String(val)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
case "string":
|
case "string":
|
||||||
return String(val);
|
return String(val);
|
||||||
case "path":
|
case "path":
|
||||||
@@ -240,15 +274,6 @@ if (["true", "1"].includes(uiSsl)) {
|
|||||||
setConfig("UI_SSL_KEY", `${d}/cwmp.key`);
|
setConfig("UI_SSL_KEY", `${d}/cwmp.key`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fsHostname) {
|
|
||||||
const FS_PORT = allConfig["FS_PORT"] || 7567;
|
|
||||||
const FS_SSL = !!allConfig["FS_SSL_CERT"];
|
|
||||||
setConfig(
|
|
||||||
"FS_URL_PREFIX",
|
|
||||||
(FS_SSL ? "https" : "http") + `://${fsHostname}:${FS_PORT}/`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
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"]);
|
||||||
@@ -284,7 +309,7 @@ export function get(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefault(optionName: string): string | number | boolean {
|
export function getDefault(optionName): string | number | boolean {
|
||||||
const option = options[optionName];
|
const option = options[optionName];
|
||||||
if (!option) return null;
|
if (!option) return null;
|
||||||
|
|
||||||
|
|||||||
+38
-18
@@ -29,13 +29,15 @@ import * as debug from "./debug";
|
|||||||
|
|
||||||
async function extractAuth(
|
async function extractAuth(
|
||||||
exp: Expression,
|
exp: Expression,
|
||||||
|
context: {},
|
||||||
|
now: number,
|
||||||
dflt: any
|
dflt: any
|
||||||
): Promise<[string, string, Expression]> {
|
): Promise<[string, string, Expression]> {
|
||||||
let username, password;
|
let username, password;
|
||||||
const _exp = await evaluateAsync(
|
const _exp = await evaluateAsync(
|
||||||
exp,
|
exp,
|
||||||
{},
|
context,
|
||||||
0,
|
now,
|
||||||
async (e: Expression): Promise<Expression> => {
|
async (e: Expression): Promise<Expression> => {
|
||||||
if (!username && Array.isArray(e) && e[0] === "FUNC") {
|
if (!username && Array.isArray(e) && e[0] === "FUNC") {
|
||||||
if (e[1] === "EXT") {
|
if (e[1] === "EXT") {
|
||||||
@@ -48,8 +50,8 @@ async function extractAuth(
|
|||||||
return fault ? null : value;
|
return fault ? null : value;
|
||||||
} else if (e[1] === "AUTH") {
|
} else if (e[1] === "AUTH") {
|
||||||
if (!Array.isArray(e[2]) && !Array.isArray(e[3])) {
|
if (!Array.isArray(e[2]) && !Array.isArray(e[3])) {
|
||||||
username = e[2] || "";
|
username = e[2];
|
||||||
password = e[3] || "";
|
password = e[3];
|
||||||
}
|
}
|
||||||
return dflt;
|
return dflt;
|
||||||
}
|
}
|
||||||
@@ -65,10 +67,10 @@ function httpGet(
|
|||||||
timeout,
|
timeout,
|
||||||
_debug: boolean,
|
_debug: boolean,
|
||||||
deviceId: string
|
deviceId: string
|
||||||
): Promise<{ statusCode: number; headers: http.IncomingHttpHeaders }> {
|
): Promise<{ statusCode: number; headers: {} }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const req = http
|
const req = http
|
||||||
.get(options, (res) => {
|
.get(options, res => {
|
||||||
res.resume();
|
res.resume();
|
||||||
resolve({ statusCode: res.statusCode, headers: res.headers });
|
resolve({ statusCode: res.statusCode, headers: res.headers });
|
||||||
if (_debug) {
|
if (_debug) {
|
||||||
@@ -76,12 +78,12 @@ function httpGet(
|
|||||||
debug.incomingHttpResponse(res, deviceId, null);
|
debug.incomingHttpResponse(res, deviceId, null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("error", (err) => {
|
.on("error", err => {
|
||||||
req.abort();
|
req.abort();
|
||||||
reject(new Error("Device is offline"));
|
reject(new Error("Device is offline"));
|
||||||
if (_debug) debug.outgoingHttpRequestError(req, deviceId, options, err);
|
if (_debug) debug.outgoingHttpRequestError(req, deviceId, options, err);
|
||||||
})
|
})
|
||||||
.on("socket", (socket) => {
|
.on("socket", socket => {
|
||||||
socket.setTimeout(timeout);
|
socket.setTimeout(timeout);
|
||||||
socket.on("timeout", () => {
|
socket.on("timeout", () => {
|
||||||
req.abort();
|
req.abort();
|
||||||
@@ -93,21 +95,23 @@ function httpGet(
|
|||||||
export async function httpConnectionRequest(
|
export async function httpConnectionRequest(
|
||||||
address: string,
|
address: string,
|
||||||
authExp: Expression,
|
authExp: Expression,
|
||||||
|
context: {},
|
||||||
allowBasicAuth: boolean,
|
allowBasicAuth: boolean,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
_debug: boolean,
|
_debug: boolean,
|
||||||
deviceId: string
|
deviceId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const now = Date.now();
|
||||||
const options: http.RequestOptions = parse(address);
|
const options: http.RequestOptions = parse(address);
|
||||||
if (options.protocol !== "http:")
|
if (options.protocol !== "http:")
|
||||||
throw new Error("Invalid connection request URL or protocol");
|
throw new Error("Invalid connection request URL or protocol");
|
||||||
|
|
||||||
options.agent = new http.Agent({
|
options.agent = new http.Agent({
|
||||||
maxSockets: 1,
|
maxSockets: 1,
|
||||||
keepAlive: true,
|
keepAlive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
let authHeader: Record<string, string>;
|
let authHeader: {};
|
||||||
let username: string;
|
let username: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
|
|
||||||
@@ -121,8 +125,8 @@ export async function httpConnectionRequest(
|
|||||||
opts = Object.assign(
|
opts = Object.assign(
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: auth.basic(username || "", password || ""),
|
Authorization: auth.basic(username || "", password || "")
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
@@ -137,8 +141,8 @@ export async function httpConnectionRequest(
|
|||||||
"GET",
|
"GET",
|
||||||
null,
|
null,
|
||||||
authHeader
|
authHeader
|
||||||
),
|
)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
@@ -159,7 +163,12 @@ export async function httpConnectionRequest(
|
|||||||
authHeader = auth.parseWwwAuthenticateHeader(
|
authHeader = auth.parseWwwAuthenticateHeader(
|
||||||
res.headers["www-authenticate"]
|
res.headers["www-authenticate"]
|
||||||
);
|
);
|
||||||
[username, password, authExp] = await extractAuth(authExp, false);
|
[username, password, authExp] = await extractAuth(
|
||||||
|
authExp,
|
||||||
|
context,
|
||||||
|
now,
|
||||||
|
false
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected response code from device: ${res.statusCode}`
|
`Unexpected response code from device: ${res.statusCode}`
|
||||||
@@ -172,7 +181,8 @@ export async function httpConnectionRequest(
|
|||||||
export async function udpConnectionRequest(
|
export async function udpConnectionRequest(
|
||||||
address: string,
|
address: string,
|
||||||
authExp: Expression,
|
authExp: Expression,
|
||||||
sourcePort = 0,
|
context: {},
|
||||||
|
sourcePort: number = 0,
|
||||||
_debug: boolean,
|
_debug: boolean,
|
||||||
deviceId: string
|
deviceId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -192,7 +202,12 @@ export async function udpConnectionRequest(
|
|||||||
let username: string;
|
let username: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
|
|
||||||
[username, password, authExp] = await extractAuth(authExp, null);
|
[username, password, authExp] = await extractAuth(
|
||||||
|
authExp,
|
||||||
|
context,
|
||||||
|
now,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
if (username == null) username = "";
|
if (username == null) username = "";
|
||||||
if (password == null) password = "";
|
if (password == null) password = "";
|
||||||
@@ -218,7 +233,12 @@ export async function udpConnectionRequest(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[username, password, authExp] = await extractAuth(authExp, null);
|
[username, password, authExp] = await extractAuth(
|
||||||
|
authExp,
|
||||||
|
context,
|
||||||
|
now,
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
+180
-288
@@ -23,9 +23,11 @@ import { Socket } from "net";
|
|||||||
import * as auth from "./auth";
|
import * as auth from "./auth";
|
||||||
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 soapCustom from "./soap";
|
||||||
|
import * as soapLibxmljs from "./soap-libxmljs";
|
||||||
import * as session from "./session";
|
import * as session from "./session";
|
||||||
import { evaluateAsync, evaluate, extractParams } from "./common/expression";
|
import { evaluateAsync, evaluate, extractParams } from "./common/expression";
|
||||||
|
import * as device from "./device";
|
||||||
import * as cache from "./cache";
|
import * as cache from "./cache";
|
||||||
import * as localCache from "./local-cache";
|
import * as localCache from "./local-cache";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
@@ -36,24 +38,27 @@ import * as extensions from "./extensions";
|
|||||||
import {
|
import {
|
||||||
SessionContext,
|
SessionContext,
|
||||||
AcsRequest,
|
AcsRequest,
|
||||||
|
SetAcsRequest,
|
||||||
SessionFault,
|
SessionFault,
|
||||||
Operation,
|
Operation,
|
||||||
Fault,
|
Fault,
|
||||||
Expression,
|
Expression,
|
||||||
Task,
|
Task,
|
||||||
SoapMessage,
|
SoapMessage
|
||||||
InformRequest,
|
|
||||||
Preset,
|
|
||||||
GetRPCMethodsResponse,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { IncomingMessage, ServerResponse } from "http";
|
import { IncomingMessage, ServerResponse } from "http";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
import { TLSSocket } from "tls";
|
||||||
import { decode, encodingExists } from "iconv-lite";
|
import { decode, encodingExists } from "iconv-lite";
|
||||||
|
import { dependencies as DEPENDENCIES } from "../package.json";
|
||||||
import { parseXmlDeclaration } from "./xml-parser";
|
import { parseXmlDeclaration } from "./xml-parser";
|
||||||
import * as debug from "./debug";
|
import * as debug from "./debug";
|
||||||
import { getRequestOrigin } from "./forwarded";
|
|
||||||
|
|
||||||
|
const soap =
|
||||||
|
DEPENDENCIES["libxmljs"] && config.get("XML_LIBXMLJS")
|
||||||
|
? soapLibxmljs
|
||||||
|
: soapCustom;
|
||||||
const gzipPromisified = promisify(zlib.gzip);
|
const gzipPromisified = promisify(zlib.gzip);
|
||||||
const deflatePromisified = promisify(zlib.deflate);
|
const deflatePromisified = promisify(zlib.deflate);
|
||||||
|
|
||||||
@@ -68,7 +73,7 @@ const stats = {
|
|||||||
concurrentRequests: 0,
|
concurrentRequests: 0,
|
||||||
totalRequests: 0,
|
totalRequests: 0,
|
||||||
droppedRequests: 0,
|
droppedRequests: 0,
|
||||||
initiatedSessions: 0,
|
initiatedSessions: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
async function authenticate(
|
async function authenticate(
|
||||||
@@ -104,12 +109,12 @@ async function authenticate(
|
|||||||
authentication["body"] = body;
|
authentication["body"] = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
const res = await evaluateAsync(
|
const res = await evaluateAsync(
|
||||||
authExpression,
|
authExpression,
|
||||||
{},
|
sessionContext.configContext,
|
||||||
sessionContext.timestamp,
|
now,
|
||||||
async (e: Expression): Promise<Expression> => {
|
async (e: Expression): Promise<Expression> => {
|
||||||
e = session.configContextCallback(sessionContext, e);
|
|
||||||
if (Array.isArray(e) && e[0] === "FUNC") {
|
if (Array.isArray(e) && e[0] === "FUNC") {
|
||||||
if (e[1] === "EXT") {
|
if (e[1] === "EXT") {
|
||||||
if (typeof e[2] !== "string" || typeof e[3] !== "string") return null;
|
if (typeof e[2] !== "string" || typeof e[3] !== "string") return null;
|
||||||
@@ -196,14 +201,14 @@ async function writeResponse(
|
|||||||
if (connection.destroyed) {
|
if (connection.destroyed) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "Connection dropped",
|
message: "Connection dropped"
|
||||||
});
|
});
|
||||||
} else if (close) {
|
} else if (close) {
|
||||||
const isNew = await endSession(sessionContext);
|
const isNew = await endSession(sessionContext);
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "New device registered",
|
message: "New device registered"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -230,12 +235,8 @@ function recordFault(
|
|||||||
channels = sessionContext.channels;
|
channels = sessionContext.channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelKeys = Object.keys(channels);
|
|
||||||
if (!channelKeys.length)
|
|
||||||
throw new Error("Fault not associated with a channel!");
|
|
||||||
|
|
||||||
const faults = sessionContext.faults;
|
const faults = sessionContext.faults;
|
||||||
for (const channel of channelKeys) {
|
for (const channel of Object.keys(channels)) {
|
||||||
const provs = sessionContext.faults[channel]
|
const provs = sessionContext.faults[channel]
|
||||||
? sessionContext.faults[channel].provisions
|
? sessionContext.faults[channel].provisions
|
||||||
: [];
|
: [];
|
||||||
@@ -253,7 +254,7 @@ function recordFault(
|
|||||||
++sessionContext.retries[channel];
|
++sessionContext.retries[channel];
|
||||||
} else {
|
} else {
|
||||||
sessionContext.retries[channel] = 0;
|
sessionContext.retries[channel] = 0;
|
||||||
if (channelKeys.length !== 1) faults[channel].retryNow = true;
|
if (Object.keys(channels).length !== 1) faults[channel].retryNow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channels[channel] === 0) faults[channel].precondition = true;
|
if (channels[channel] === 0) faults[channel].precondition = true;
|
||||||
@@ -266,18 +267,18 @@ function recordFault(
|
|||||||
message: "Channel has faulted",
|
message: "Channel has faulted",
|
||||||
fault: fault,
|
fault: fault,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
retries: sessionContext.retries[channel],
|
retries: sessionContext.retries[channel]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < provisions.length; ++i) {
|
for (let i = 0; i < provisions.length; ++i) {
|
||||||
for (const channel of channelKeys) {
|
for (const channel of Object.keys(channels)) {
|
||||||
if ((channels[channel] >> i) & 1)
|
if ((channels[channel] >> i) & 1)
|
||||||
faults[channel].provisions.push(provisions[i]);
|
faults[channel].provisions.push(provisions[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const channel of channelKeys) {
|
for (const channel of Object.keys(channels)) {
|
||||||
const provs = faults[channel].provisions;
|
const provs = faults[channel].provisions;
|
||||||
faults[channel].provisions = [];
|
faults[channel].provisions = [];
|
||||||
appendProvisions(faults[channel].provisions, provs);
|
appendProvisions(faults[channel].provisions, provs);
|
||||||
@@ -286,27 +287,20 @@ function recordFault(
|
|||||||
session.clearProvisions(sessionContext);
|
session.clearProvisions(sessionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function inform(
|
async function inform(sessionContext: SessionContext, rpc): Promise<void> {
|
||||||
sessionContext: SessionContext,
|
const acsResponse = await session.inform(sessionContext, rpc.cpeRequest);
|
||||||
rpc: SoapMessage
|
sessionContext.state = 1;
|
||||||
): Promise<{ code: number; headers: Record<string, string>; data: string }> {
|
|
||||||
const acsResponse = await session.inform(
|
|
||||||
sessionContext,
|
|
||||||
rpc.cpeRequest as InformRequest
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = soap.response({
|
const res = soap.response({
|
||||||
id: rpc.id,
|
id: rpc.id,
|
||||||
acsResponse: acsResponse,
|
acsResponse: acsResponse,
|
||||||
cwmpVersion: sessionContext.cwmpVersion,
|
cwmpVersion: sessionContext.cwmpVersion
|
||||||
});
|
});
|
||||||
|
|
||||||
const cookiesPath = localCache.getConfig(
|
const cookiesPath = localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.cookiesPath",
|
"cwmp.cookiesPath",
|
||||||
{},
|
sessionContext.configContext
|
||||||
sessionContext.timestamp,
|
|
||||||
(e) => session.configContextCallback(sessionContext, e)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cookiesPath) {
|
if (cookiesPath) {
|
||||||
@@ -317,7 +311,7 @@ async function inform(
|
|||||||
res.headers["Set-Cookie"] = `session=${sessionContext.sessionId}`;
|
res.headers["Set-Cookie"] = `session=${sessionContext.sessionId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return writeResponse(sessionContext, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function transferComplete(sessionContext, rpc): Promise<void> {
|
async function transferComplete(sessionContext, rpc): Promise<void> {
|
||||||
@@ -330,7 +324,7 @@ async function transferComplete(sessionContext, rpc): Promise<void> {
|
|||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "Unrecognized command key",
|
message: "Unrecognized command key",
|
||||||
rpc: rpc,
|
rpc: rpc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +341,7 @@ async function transferComplete(sessionContext, rpc): Promise<void> {
|
|||||||
const res = soap.response({
|
const res = soap.response({
|
||||||
id: rpc.id,
|
id: rpc.id,
|
||||||
acsResponse: acsResponse,
|
acsResponse: acsResponse,
|
||||||
cwmpVersion: sessionContext.cwmpVersion,
|
cwmpVersion: sessionContext.cwmpVersion
|
||||||
});
|
});
|
||||||
|
|
||||||
return writeResponse(sessionContext, res);
|
return writeResponse(sessionContext, res);
|
||||||
@@ -396,9 +390,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
|
|||||||
const RETRY_DELAY = +localCache.getConfig(
|
const RETRY_DELAY = +localCache.getConfig(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.retryDelay",
|
"cwmp.retryDelay",
|
||||||
{},
|
sessionContext.configContext
|
||||||
sessionContext.timestamp,
|
|
||||||
(e) => session.configContextCallback(sessionContext, e)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sessionContext.faults) {
|
if (sessionContext.faults) {
|
||||||
@@ -431,7 +423,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parameters: { [name: string]: Path } = {};
|
const parameters: { [name: string]: Path } = {};
|
||||||
const filteredPresets: Preset[] = [];
|
const filteredPresets = [];
|
||||||
|
|
||||||
for (const preset of presets) {
|
for (const preset of presets) {
|
||||||
if (whiteList != null) {
|
if (whiteList != null) {
|
||||||
@@ -460,33 +452,17 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filteredPresets.push(preset);
|
filteredPresets.push(preset);
|
||||||
for (const k of extractParams(
|
for (const k of extractParams(preset.precondition))
|
||||||
evaluate(preset.precondition, null, sessionContext.timestamp)
|
|
||||||
)) {
|
|
||||||
// Mark channel in case of fault during fetching precondition
|
|
||||||
sessionContext.channels[preset.channel] = 0;
|
|
||||||
if (typeof k === "string") parameters[k] = Path.parse(k);
|
if (typeof k === "string") parameters[k] = Path.parse(k);
|
||||||
}
|
|
||||||
for (const prov of preset.provisions) {
|
|
||||||
for (const arg of prov.slice(1)) {
|
|
||||||
for (const k of extractParams(
|
|
||||||
evaluate(arg, null, sessionContext.timestamp)
|
|
||||||
)) {
|
|
||||||
// Mark channel in case of fault during fetching precondition
|
|
||||||
sessionContext.channels[preset.channel] = 0;
|
|
||||||
if (typeof k === "string") parameters[k] = Path.parse(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const declarations = Object.values(parameters).map((v) => ({
|
const declarations = Object.values(parameters).map(v => ({
|
||||||
path: v,
|
path: v,
|
||||||
pathGet: 1,
|
pathGet: 1,
|
||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { value: 1 },
|
attrGet: { value: 1 },
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { fault: flt, rpcId: reqId, rpc: acsReq } = await session.rpcRequest(
|
const { fault: flt, rpcId: reqId, rpc: acsReq } = await session.rpcRequest(
|
||||||
@@ -504,32 +480,28 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
|
|||||||
|
|
||||||
session.clearProvisions(sessionContext);
|
session.clearProvisions(sessionContext);
|
||||||
|
|
||||||
|
const parameterValues = {};
|
||||||
|
for (const [k, v] of Object.entries(parameters)) {
|
||||||
|
const unpacked = device.unpack(deviceData, v);
|
||||||
|
if (!unpacked.length) continue;
|
||||||
|
const attrs = deviceData.attributes.get(unpacked[0]);
|
||||||
|
if (attrs && attrs.value && attrs.value[1])
|
||||||
|
parameterValues[k] = attrs.value[1][0];
|
||||||
|
}
|
||||||
|
|
||||||
if (whiteList != null)
|
if (whiteList != null)
|
||||||
session.addProvisions(sessionContext, whiteList, whiteListProvisions);
|
session.addProvisions(sessionContext, whiteList, whiteListProvisions);
|
||||||
|
|
||||||
const appendProvisionsToFaults = {};
|
const appendProvisionsToFaults = {};
|
||||||
|
const now = Date.now();
|
||||||
for (const p of filteredPresets) {
|
for (const p of filteredPresets) {
|
||||||
if (
|
if (evaluate(p.precondition, parameterValues, now)) {
|
||||||
evaluate(p.precondition, {}, sessionContext.timestamp, (e) =>
|
|
||||||
session.configContextCallback(sessionContext, e)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const provs = p.provisions.map((pp) => [
|
|
||||||
pp[0],
|
|
||||||
...pp
|
|
||||||
.slice(1)
|
|
||||||
.map((arg) =>
|
|
||||||
evaluate(arg, {}, sessionContext.timestamp, (e) =>
|
|
||||||
session.configContextCallback(sessionContext, e)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
if (blackList[p.channel] === 2) {
|
if (blackList[p.channel] === 2) {
|
||||||
appendProvisionsToFaults[p.channel] = (
|
appendProvisionsToFaults[p.channel] = (
|
||||||
appendProvisionsToFaults[p.channel] || []
|
appendProvisionsToFaults[p.channel] || []
|
||||||
).concat(provs);
|
).concat(p.provisions);
|
||||||
} else {
|
} else {
|
||||||
session.addProvisions(sessionContext, p.channel, provs);
|
session.addProvisions(sessionContext, p.channel, p.provisions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,7 +525,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
|
|||||||
const fault = {
|
const fault = {
|
||||||
code: "preset_loop",
|
code: "preset_loop",
|
||||||
message: "The presets are stuck in an endless configuration loop",
|
message: "The presets are stuck in an endless configuration loop",
|
||||||
timestamp: sessionContext.timestamp,
|
timestamp: sessionContext.timestamp
|
||||||
};
|
};
|
||||||
recordFault(sessionContext, fault);
|
recordFault(sessionContext, fault);
|
||||||
// No need to save retryNow
|
// No need to save retryNow
|
||||||
@@ -634,13 +606,13 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
|
|||||||
session.clearProvisions(sessionContext);
|
session.clearProvisions(sessionContext);
|
||||||
|
|
||||||
// Clear expired tasks
|
// Clear expired tasks
|
||||||
sessionContext.tasks = sessionContext.tasks.filter((task) => {
|
sessionContext.tasks = sessionContext.tasks.filter(task => {
|
||||||
if (!(task.expiry <= sessionContext.timestamp)) return true;
|
if (!(task.expiry <= sessionContext.timestamp)) return true;
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "Task expired",
|
message: "Task expired",
|
||||||
task: task,
|
task: task
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!sessionContext.doneTasks) sessionContext.doneTasks = [];
|
if (!sessionContext.doneTasks) sessionContext.doneTasks = [];
|
||||||
@@ -657,7 +629,7 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const task = sessionContext.tasks.find(
|
const task = sessionContext.tasks.find(
|
||||||
(t) => !sessionContext.faults[`task_${t._id}`]
|
t => !sessionContext.faults[`task_${t._id}`]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!task) return applyPresets(sessionContext);
|
if (!task) return applyPresets(sessionContext);
|
||||||
@@ -670,7 +642,7 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
|
|||||||
sessionContext.channels[`task_${task._id}`] = 0;
|
sessionContext.channels[`task_${task._id}`] = 0;
|
||||||
for (const p of task.parameterNames) {
|
for (const p of task.parameterNames) {
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["refresh", p],
|
["refresh", p]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,14 +652,14 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
|
|||||||
sessionContext.channels[`task_${task._id}`] = 0;
|
sessionContext.channels[`task_${task._id}`] = 0;
|
||||||
for (const p of task.parameterValues) {
|
for (const p of task.parameterValues) {
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["value", p[0], p[1]],
|
["value", p[0], p[1]]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "refreshObject":
|
case "refreshObject":
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["refresh", task.objectName],
|
["refresh", task.objectName]
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "reboot":
|
case "reboot":
|
||||||
@@ -698,20 +670,20 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
|
|||||||
break;
|
break;
|
||||||
case "download":
|
case "download":
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["download", task.fileType, task.fileName, task.targetFileName || ""],
|
["download", task.fileType, task.fileName, task.targetFileName || ""]
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "addObject":
|
case "addObject":
|
||||||
alias = (task.parameterValues || [])
|
alias = (task.parameterValues || [])
|
||||||
.map((p) => `${p[0]}:${JSON.stringify(p[1])}`)
|
.map(p => `${p[0]}:${JSON.stringify(p[1])}`)
|
||||||
.join(",");
|
.join(",");
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["instances", `${task.objectName}.[${alias}]`, "+1"],
|
["instances", `${task.objectName}.[${alias}]`, "+1"]
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "deleteObject":
|
case "deleteObject":
|
||||||
session.addProvisions(sessionContext, `task_${task._id}`, [
|
session.addProvisions(sessionContext, `task_${task._id}`, [
|
||||||
["instances", task.objectName, 0],
|
["instances", task.objectName, 0]
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "provisions":
|
case "provisions":
|
||||||
@@ -805,37 +777,38 @@ async function sendAcsRequest(
|
|||||||
return writeResponse(sessionContext, soap.response(null), true);
|
return 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;
|
||||||
let prefix = "" + config.get("FS_URL_PREFIX");
|
if (!downloadRequest.url) {
|
||||||
|
const FS_PORT = config.get("FS_PORT");
|
||||||
if (!prefix) {
|
const FS_SSL = config.get("FS_SSL_CERT");
|
||||||
const FS_PORT = +config.get("FS_PORT");
|
let hostname = config.get("FS_HOSTNAME");
|
||||||
const ssl = !!config.get("FS_SSL_CERT");
|
if (!hostname) {
|
||||||
const origin = getRequestOrigin(sessionContext.httpRequest);
|
if (sessionContext.httpRequest.headers["host"])
|
||||||
let hostname = origin.localAddress;
|
hostname = sessionContext.httpRequest.headers["host"].split(":")[0];
|
||||||
if (origin.host) [hostname] = origin.host.split(":", 1);
|
else hostname = sessionContext.httpRequest.connection.localAddress;
|
||||||
prefix = (ssl ? "https" : "http") + `://${hostname}:${FS_PORT}/`;
|
|
||||||
}
|
}
|
||||||
|
downloadRequest.url = FS_SSL ? "https://" : "http://";
|
||||||
acsRequest.url = prefix + encodeURI(acsRequest.fileName);
|
downloadRequest.url += hostname;
|
||||||
|
if (FS_PORT !== 80) downloadRequest.url += ":" + FS_PORT;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpc = {
|
const rpc = {
|
||||||
id: id,
|
id: id,
|
||||||
acsRequest: acsRequest,
|
acsRequest: acsRequest,
|
||||||
cwmpVersion: sessionContext.cwmpVersion,
|
cwmpVersion: sessionContext.cwmpVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "ACS request",
|
message: "ACS request",
|
||||||
rpc: rpc,
|
rpc: rpc
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = soap.response(rpc);
|
const res = soap.response(rpc);
|
||||||
@@ -851,7 +824,7 @@ async function getSession(connection, sessionId): Promise<SessionContext> {
|
|||||||
|
|
||||||
if (!sessionId) return null;
|
if (!sessionId) return null;
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
const sessionContextString = await cache.pop(`session_${sessionId}`);
|
const sessionContextString = await cache.pop(`session_${sessionId}`);
|
||||||
if (!sessionContextString) return null;
|
if (!sessionContextString) return null;
|
||||||
@@ -878,7 +851,7 @@ export function onConnection(socket: Socket): void {
|
|||||||
const timeoutMsg = logger.flatten({
|
const timeoutMsg = logger.flatten({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "Session timeout",
|
message: "Session timeout",
|
||||||
sessionTimestamp: sessionContext.timestamp,
|
sessionTimestamp: sessionContext.timestamp
|
||||||
});
|
});
|
||||||
|
|
||||||
const timeout =
|
const timeout =
|
||||||
@@ -913,7 +886,7 @@ setInterval(() => {
|
|||||||
droppedRequests: stats.droppedRequests,
|
droppedRequests: stats.droppedRequests,
|
||||||
totalRequests: stats.totalRequests,
|
totalRequests: stats.totalRequests,
|
||||||
initiatedSessions: stats.initiatedSessions,
|
initiatedSessions: stats.initiatedSessions,
|
||||||
pid: process.pid,
|
pid: process.pid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,20 +911,20 @@ async function getDueTasksAndFaultsAndOperations(
|
|||||||
tasks: resParsed.tasks || [],
|
tasks: resParsed.tasks || [],
|
||||||
faults: resParsed.faults || {},
|
faults: resParsed.faults || {},
|
||||||
operations: resParsed.operations || {},
|
operations: resParsed.operations || {},
|
||||||
ttl: 0,
|
ttl: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const res2 = await Promise.all([
|
const res2 = await Promise.all([
|
||||||
db.getDueTasks(deviceId, timestamp),
|
db.getDueTasks(deviceId, timestamp),
|
||||||
db.getFaults(deviceId),
|
db.getFaults(deviceId),
|
||||||
db.getOperations(deviceId),
|
db.getOperations(deviceId)
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
tasks: res2[0][0],
|
tasks: res2[0][0],
|
||||||
faults: res2[1],
|
faults: res2[1],
|
||||||
operations: res2[2],
|
operations: res2[2],
|
||||||
ttl: res2[0][1] || 0,
|
ttl: res2[0][1] || 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,7 +938,7 @@ async function cacheDueTasksAndFaultsAndOperations(
|
|||||||
const v = {
|
const v = {
|
||||||
tasks: null,
|
tasks: null,
|
||||||
faults: null,
|
faults: null,
|
||||||
operations: null,
|
operations: null
|
||||||
};
|
};
|
||||||
if (tasks.length) v.tasks = tasks;
|
if (tasks.length) v.tasks = tasks;
|
||||||
if (Object.keys(faults).length) v.faults = faults;
|
if (Object.keys(faults).length) v.faults = faults;
|
||||||
@@ -985,7 +958,7 @@ async function cacheDueTasksAndFaultsAndOperations(
|
|||||||
async function reportBadState(sessionContext: SessionContext): Promise<void> {
|
async function reportBadState(sessionContext: SessionContext): Promise<void> {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Bad session state",
|
message: "Bad session state",
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext
|
||||||
});
|
});
|
||||||
const httpResponse = sessionContext.httpResponse;
|
const httpResponse = sessionContext.httpResponse;
|
||||||
currentSessions.delete(httpResponse.connection);
|
currentSessions.delete(httpResponse.connection);
|
||||||
@@ -998,20 +971,20 @@ async function reportBadState(sessionContext: SessionContext): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function responseUnauthorized(
|
async function responseUnauthorized(
|
||||||
sessionContext: SessionContext,
|
sessionContext: SessionContext
|
||||||
close: boolean
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const authHeader = sessionContext.httpRequest.headers["authorization"];
|
||||||
const resHeaders = {};
|
const resHeaders = {};
|
||||||
if (close) {
|
if (authHeader) {
|
||||||
// Invalid credentials
|
// Invalid credentials
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Authentication failure",
|
message: "Authentication failure",
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext
|
||||||
});
|
});
|
||||||
|
|
||||||
resHeaders["Connection"] = "close";
|
resHeaders["Connection"] = "close";
|
||||||
} else {
|
} else {
|
||||||
if (getRequestOrigin(sessionContext.httpRequest).encrypted) {
|
if (sessionContext.httpRequest.socket instanceof TLSSocket) {
|
||||||
resHeaders["WWW-Authenticate"] = `Basic realm="${REALM}"`;
|
resHeaders["WWW-Authenticate"] = `Basic realm="${REALM}"`;
|
||||||
} else {
|
} else {
|
||||||
const nonce = crypto.randomBytes(16).toString("hex");
|
const nonce = crypto.randomBytes(16).toString("hex");
|
||||||
@@ -1036,100 +1009,25 @@ async function responseUnauthorized(
|
|||||||
|
|
||||||
async function processRequest(
|
async function processRequest(
|
||||||
sessionContext: SessionContext,
|
sessionContext: SessionContext,
|
||||||
rpc: SoapMessage,
|
rpc: SoapMessage
|
||||||
parseWarnings: Record<string, unknown>[],
|
|
||||||
body: string
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
for (const w of parseWarnings) {
|
|
||||||
w.sessionContext = sessionContext;
|
|
||||||
w.rpc = rpc;
|
|
||||||
logger.accessWarn(w);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionContext.state === 0) {
|
|
||||||
if (!rpc.cpeRequest || rpc.cpeRequest.name !== "Inform")
|
|
||||||
return reportBadState(sessionContext);
|
|
||||||
|
|
||||||
const res = await inform(sessionContext, rpc);
|
|
||||||
|
|
||||||
sessionContext.debug = !!localCache.getConfig(
|
|
||||||
sessionContext.cacheSnapshot,
|
|
||||||
"cwmp.debug",
|
|
||||||
{},
|
|
||||||
sessionContext.timestamp,
|
|
||||||
(e) => session.configContextCallback(sessionContext, e)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!sessionContext.timeout) {
|
|
||||||
sessionContext.timeout = +localCache.getConfig(
|
|
||||||
sessionContext.cacheSnapshot,
|
|
||||||
"cwmp.sessionTimeout",
|
|
||||||
{},
|
|
||||||
sessionContext.timestamp,
|
|
||||||
(e) => session.configContextCallback(sessionContext, e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionContext.debug) {
|
|
||||||
debug.incomingHttpRequest(
|
|
||||||
sessionContext.httpRequest,
|
|
||||||
sessionContext.deviceId,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const authenticated = await authenticate(sessionContext, body);
|
|
||||||
if (!authenticated) {
|
|
||||||
if (!sessionContext.authState) {
|
|
||||||
sessionContext.authState = 1;
|
|
||||||
return responseUnauthorized(sessionContext, false);
|
|
||||||
} else {
|
|
||||||
return responseUnauthorized(sessionContext, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionContext.state = 1;
|
|
||||||
sessionContext.authState = 2;
|
|
||||||
|
|
||||||
logger.accessInfo({
|
|
||||||
sessionContext: sessionContext,
|
|
||||||
message: "Inform",
|
|
||||||
rpc: rpc,
|
|
||||||
});
|
|
||||||
|
|
||||||
return writeResponse(sessionContext, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionContext.debug) {
|
|
||||||
debug.incomingHttpRequest(
|
|
||||||
sessionContext.httpRequest,
|
|
||||||
sessionContext.deviceId,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reauthenticate in case of new connection
|
|
||||||
if (sessionContext.authState !== 2) {
|
|
||||||
const authenticated = await authenticate(sessionContext, body);
|
|
||||||
if (!authenticated) {
|
|
||||||
if (!sessionContext.authState) {
|
|
||||||
sessionContext.authState = 1;
|
|
||||||
return responseUnauthorized(sessionContext, false);
|
|
||||||
} else {
|
|
||||||
return responseUnauthorized(sessionContext, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sessionContext.authState = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rpc.cpeRequest) {
|
if (rpc.cpeRequest) {
|
||||||
if (rpc.cpeRequest.name === "TransferComplete") {
|
if (rpc.cpeRequest.name === "Inform") {
|
||||||
|
if (sessionContext.state !== 0) return reportBadState(sessionContext);
|
||||||
|
|
||||||
|
logger.accessInfo({
|
||||||
|
sessionContext: sessionContext,
|
||||||
|
message: "Inform",
|
||||||
|
rpc: rpc
|
||||||
|
});
|
||||||
|
return inform(sessionContext, rpc);
|
||||||
|
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
||||||
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE request",
|
message: "CPE request",
|
||||||
rpc: rpc,
|
rpc: rpc
|
||||||
});
|
});
|
||||||
return transferComplete(sessionContext, rpc);
|
return transferComplete(sessionContext, rpc);
|
||||||
} else if (rpc.cpeRequest.name === "GetRPCMethods") {
|
} else if (rpc.cpeRequest.name === "GetRPCMethods") {
|
||||||
@@ -1138,19 +1036,19 @@ async function processRequest(
|
|||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE request",
|
message: "CPE request",
|
||||||
rpc: rpc,
|
rpc: rpc
|
||||||
});
|
});
|
||||||
const res = soap.response({
|
const res = soap.response({
|
||||||
id: rpc.id,
|
id: rpc.id,
|
||||||
acsResponse: {
|
acsResponse: {
|
||||||
name: "GetRPCMethodsResponse",
|
name: "GetRPCMethodsResponse",
|
||||||
methodList: ["Inform", "GetRPCMethods", "TransferComplete"],
|
methodList: ["Inform", "GetRPCMethods", "TransferComplete"]
|
||||||
} as GetRPCMethodsResponse,
|
},
|
||||||
cwmpVersion: sessionContext.cwmpVersion,
|
cwmpVersion: sessionContext.cwmpVersion
|
||||||
});
|
});
|
||||||
return writeResponse(sessionContext, res);
|
return writeResponse(sessionContext, res);
|
||||||
} else {
|
} else {
|
||||||
if (sessionContext.state !== 1 || rpc.cpeRequest.name === "Inform")
|
if (sessionContext.state !== 1)
|
||||||
return void reportBadState(sessionContext);
|
return void reportBadState(sessionContext);
|
||||||
|
|
||||||
throw new Error("ACS method not supported");
|
throw new Error("ACS method not supported");
|
||||||
@@ -1166,7 +1064,7 @@ async function processRequest(
|
|||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE fault",
|
message: "CPE fault",
|
||||||
rpc: rpc,
|
rpc: rpc
|
||||||
});
|
});
|
||||||
|
|
||||||
const fault = await session.rpcFault(sessionContext, rpc.id, rpc.cpeFault);
|
const fault = await session.rpcFault(sessionContext, rpc.id, rpc.cpeFault);
|
||||||
@@ -1209,7 +1107,7 @@ export function listener(
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
stats.concurrentRequests -= 1;
|
stats.concurrentRequests -= 1;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
currentSessions.delete(httpResponse.connection);
|
currentSessions.delete(httpResponse.connection);
|
||||||
httpResponse.writeHead(500, { Connection: "close" });
|
httpResponse.writeHead(500, { Connection: "close" });
|
||||||
httpResponse.end(`${err.name}: ${err.message}`);
|
httpResponse.end(`${err.name}: ${err.message}`);
|
||||||
@@ -1238,7 +1136,7 @@ async function listenerAsync(
|
|||||||
if (httpRequest.method !== "POST") {
|
if (httpRequest.method !== "POST") {
|
||||||
httpResponse.writeHead(405, {
|
httpResponse.writeHead(405, {
|
||||||
Allow: "POST",
|
Allow: "POST",
|
||||||
Connection: "close",
|
Connection: "close"
|
||||||
});
|
});
|
||||||
httpResponse.end("405 Method Not Allowed");
|
httpResponse.end("405 Method Not Allowed");
|
||||||
return;
|
return;
|
||||||
@@ -1255,7 +1153,7 @@ async function listenerAsync(
|
|||||||
if (!sessionId && stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
|
if (!sessionId && stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
|
||||||
httpResponse.writeHead(503, {
|
httpResponse.writeHead(503, {
|
||||||
"Retry-after": 60,
|
"Retry-after": 60,
|
||||||
Connection: "close",
|
Connection: "close"
|
||||||
});
|
});
|
||||||
httpResponse.end("503 Service Unavailable");
|
httpResponse.end("503 Service Unavailable");
|
||||||
stats.droppedRequests += 1;
|
stats.droppedRequests += 1;
|
||||||
@@ -1282,7 +1180,7 @@ async function listenerAsync(
|
|||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
let bytes = 0;
|
let bytes = 0;
|
||||||
|
|
||||||
stream.on("data", (chunk) => {
|
stream.on("data", chunk => {
|
||||||
chunks.push(chunk);
|
chunks.push(chunk);
|
||||||
bytes += chunk.length;
|
bytes += chunk.length;
|
||||||
});
|
});
|
||||||
@@ -1320,7 +1218,7 @@ async function listenerAsync(
|
|||||||
) {
|
) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Invalid session",
|
message: "Invalid session",
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext
|
||||||
});
|
});
|
||||||
|
|
||||||
const _body = "Invalid session";
|
const _body = "Invalid session";
|
||||||
@@ -1336,8 +1234,6 @@ async function listenerAsync(
|
|||||||
httpResponse.end(_body);
|
httpResponse.end(_body);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newConnection && sessionContext.authState !== 1)
|
|
||||||
sessionContext.authState = 0;
|
|
||||||
} else if (stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
|
} else if (stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
|
||||||
// Check again just in case device included old session ID
|
// Check again just in case device included old session ID
|
||||||
// from the previous session
|
// from the previous session
|
||||||
@@ -1357,7 +1253,7 @@ async function listenerAsync(
|
|||||||
|
|
||||||
if (!charset) {
|
if (!charset) {
|
||||||
const parse = parseXmlDeclaration(body);
|
const parse = parseXmlDeclaration(body);
|
||||||
const e = parse ? parse.find((s) => s.localName === "encoding") : null;
|
const e = parse ? parse.find(s => s.localName === "encoding") : null;
|
||||||
charset = e ? e.value.toLowerCase() : "utf8";
|
charset = e ? e.value.toLowerCase() : "utf8";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,44 +1266,38 @@ async function listenerAsync(
|
|||||||
parseError: msg,
|
parseError: msg,
|
||||||
sessionContext: sessionContext || {
|
sessionContext: sessionContext || {
|
||||||
httpRequest: httpRequest,
|
httpRequest: httpRequest,
|
||||||
httpResponse: httpResponse,
|
httpResponse: httpResponse
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
httpResponse.setHeader("Content-Length", Buffer.byteLength(msg));
|
httpResponse.setHeader("Content-Length", Buffer.byteLength(msg));
|
||||||
httpResponse.writeHead(400, { Connection: "close" });
|
httpResponse.writeHead(400, { Connection: "close" });
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
if (sessionContext.debug) {
|
if (sessionContext.debug)
|
||||||
debug.incomingHttpRequest(
|
|
||||||
httpRequest,
|
|
||||||
sessionContext.deviceId,
|
|
||||||
body.toString()
|
|
||||||
);
|
|
||||||
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, msg);
|
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, msg);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
||||||
const d = !!localCache.getConfig(
|
const d = !!localCache.getConfig(cacheSnapshot, "cwmp.debug", {
|
||||||
cacheSnapshot,
|
remoteAddress: httpResponse.connection.remoteAddress
|
||||||
"cwmp.debug",
|
});
|
||||||
{
|
if (d) debug.outgoingHttpResponse(httpResponse, null, msg);
|
||||||
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
|
|
||||||
},
|
|
||||||
Date.now(),
|
|
||||||
(e) => {
|
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
|
|
||||||
return getRequestOrigin(httpRequest).remoteAddress;
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (d) {
|
|
||||||
debug.incomingHttpRequest(httpRequest, null, body.toString());
|
|
||||||
debug.outgoingHttpResponse(httpResponse, null, msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
httpResponse.end(msg);
|
httpResponse.end(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function parsedRpc(_sessionContext, rpc, parseWarnings): Promise<void> {
|
||||||
|
for (const w of parseWarnings) {
|
||||||
|
w.sessionContext = _sessionContext;
|
||||||
|
w.rpc = rpc;
|
||||||
|
logger.accessWarn(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sessionContext.debug)
|
||||||
|
debug.incomingHttpRequest(httpRequest, _sessionContext.deviceId, bodyStr);
|
||||||
|
|
||||||
|
return processRequest(_sessionContext, rpc);
|
||||||
|
}
|
||||||
|
|
||||||
const parseWarnings = [];
|
const parseWarnings = [];
|
||||||
let rpc;
|
let rpc;
|
||||||
try {
|
try {
|
||||||
@@ -1422,18 +1312,13 @@ async function listenerAsync(
|
|||||||
parseError: error.message.trim(),
|
parseError: error.message.trim(),
|
||||||
sessionContext: sessionContext || {
|
sessionContext: sessionContext || {
|
||||||
httpRequest: httpRequest,
|
httpRequest: httpRequest,
|
||||||
httpResponse: httpResponse,
|
httpResponse: httpResponse
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
httpResponse.setHeader("Content-Length", Buffer.byteLength(error.message));
|
httpResponse.setHeader("Content-Length", Buffer.byteLength(error.message));
|
||||||
httpResponse.writeHead(400, { Connection: "close" });
|
httpResponse.writeHead(400, { Connection: "close" });
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
if (sessionContext.debug) {
|
if (sessionContext.debug) {
|
||||||
debug.incomingHttpRequest(
|
|
||||||
httpRequest,
|
|
||||||
sessionContext.deviceId,
|
|
||||||
bodyStr
|
|
||||||
);
|
|
||||||
debug.outgoingHttpResponse(
|
debug.outgoingHttpResponse(
|
||||||
httpResponse,
|
httpResponse,
|
||||||
sessionContext.deviceId,
|
sessionContext.deviceId,
|
||||||
@@ -1442,56 +1327,42 @@ async function listenerAsync(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
||||||
const d = !!localCache.getConfig(
|
const d = !!localCache.getConfig(cacheSnapshot, "cwmp.debug", {
|
||||||
cacheSnapshot,
|
remoteAddress: httpResponse.connection.remoteAddress
|
||||||
"cwmp.debug",
|
});
|
||||||
{
|
if (d) debug.outgoingHttpResponse(httpResponse, null, error.message);
|
||||||
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
|
|
||||||
},
|
|
||||||
Date.now(),
|
|
||||||
(e) => {
|
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
|
|
||||||
return getRequestOrigin(httpRequest).remoteAddress;
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (d) {
|
|
||||||
debug.incomingHttpRequest(httpRequest, null, bodyStr);
|
|
||||||
debug.outgoingHttpResponse(httpResponse, null, error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
httpResponse.end(error.message);
|
httpResponse.end(error.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sessionContext)
|
if (sessionContext) {
|
||||||
return processRequest(sessionContext, rpc, parseWarnings, bodyStr);
|
// Authenticate in case of new connection or unauthenticated session
|
||||||
|
const authenticated =
|
||||||
|
sessionContext.state === 0 || newConnection
|
||||||
|
? await authenticate(sessionContext, bodyStr)
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (!authenticated) return responseUnauthorized(sessionContext);
|
||||||
|
|
||||||
|
return parsedRpc(sessionContext, rpc, parseWarnings);
|
||||||
|
}
|
||||||
|
|
||||||
if (!(rpc.cpeRequest && rpc.cpeRequest.name === "Inform")) {
|
if (!(rpc.cpeRequest && rpc.cpeRequest.name === "Inform")) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Invalid session",
|
message: "Invalid session",
|
||||||
sessionContext: sessionContext || {
|
sessionContext: sessionContext || {
|
||||||
httpRequest: httpRequest,
|
httpRequest: httpRequest,
|
||||||
httpResponse: httpResponse,
|
httpResponse: httpResponse
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
const _body = "Invalid session";
|
const _body = "Invalid session";
|
||||||
httpResponse.setHeader("Content-Length", Buffer.byteLength(_body));
|
httpResponse.setHeader("Content-Length", Buffer.byteLength(_body));
|
||||||
httpResponse.writeHead(400, { Connection: "close" });
|
httpResponse.writeHead(400, { Connection: "close" });
|
||||||
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
||||||
const d = !!localCache.getConfig(
|
const d = !!localCache.getConfig(cacheSnapshot, "cwmp.debug", {
|
||||||
cacheSnapshot,
|
remoteAddress: httpResponse.connection.remoteAddress
|
||||||
"cwmp.debug",
|
});
|
||||||
{
|
|
||||||
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
|
|
||||||
},
|
|
||||||
Date.now(),
|
|
||||||
(e) => {
|
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
|
|
||||||
return getRequestOrigin(httpRequest).remoteAddress;
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (d)
|
if (d)
|
||||||
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, _body);
|
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, _body);
|
||||||
httpResponse.end(_body);
|
httpResponse.end(_body);
|
||||||
@@ -1503,13 +1374,31 @@ async function listenerAsync(
|
|||||||
|
|
||||||
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
const cacheSnapshot = await localCache.getCurrentSnapshot();
|
||||||
|
|
||||||
|
const configContext = {
|
||||||
|
id: deviceId,
|
||||||
|
serialNumber: rpc.cpeRequest.deviceId["SerialNumber"],
|
||||||
|
productClass: rpc.cpeRequest.deviceId["ProductClass"],
|
||||||
|
oui: rpc.cpeRequest.deviceId["OUI"],
|
||||||
|
remoteAddress: httpRequest.connection.remoteAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
const sessionTimeout =
|
||||||
|
rpc.sessionTimeout ||
|
||||||
|
localCache.getConfig(cacheSnapshot, "cwmp.sessionTimeout", configContext);
|
||||||
|
|
||||||
const _sessionContext = session.init(
|
const _sessionContext = session.init(
|
||||||
deviceId,
|
deviceId,
|
||||||
rpc.cwmpVersion,
|
rpc.cwmpVersion,
|
||||||
rpc.sessionTimeout
|
sessionTimeout
|
||||||
);
|
);
|
||||||
|
|
||||||
_sessionContext.cacheSnapshot = cacheSnapshot;
|
_sessionContext.cacheSnapshot = cacheSnapshot;
|
||||||
|
_sessionContext.configContext = configContext;
|
||||||
|
_sessionContext.debug = !!localCache.getConfig(
|
||||||
|
cacheSnapshot,
|
||||||
|
"cwmp.debug",
|
||||||
|
configContext
|
||||||
|
);
|
||||||
|
|
||||||
_sessionContext.httpRequest = httpRequest;
|
_sessionContext.httpRequest = httpRequest;
|
||||||
_sessionContext.httpResponse = httpResponse;
|
_sessionContext.httpResponse = httpResponse;
|
||||||
@@ -1520,7 +1409,7 @@ async function listenerAsync(
|
|||||||
tasks: dueTasks,
|
tasks: dueTasks,
|
||||||
faults,
|
faults,
|
||||||
operations,
|
operations,
|
||||||
ttl: cacheUntil,
|
ttl: cacheUntil
|
||||||
} = await getDueTasksAndFaultsAndOperations(
|
} = await getDueTasksAndFaultsAndOperations(
|
||||||
deviceId,
|
deviceId,
|
||||||
_sessionContext.timestamp
|
_sessionContext.timestamp
|
||||||
@@ -1558,5 +1447,8 @@ async function listenerAsync(
|
|||||||
_sessionContext.new = true;
|
_sessionContext.new = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return processRequest(_sessionContext, rpc, parseWarnings, bodyStr);
|
const authenticated = await authenticate(_sessionContext, bodyStr);
|
||||||
|
if (!authenticated) return responseUnauthorized(_sessionContext);
|
||||||
|
|
||||||
|
parsedRpc(_sessionContext, rpc, parseWarnings);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
SessionFault,
|
SessionFault,
|
||||||
Task,
|
Task,
|
||||||
Operation,
|
Operation,
|
||||||
Expression,
|
Expression
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import Path from "./common/path";
|
import Path from "./common/path";
|
||||||
|
|
||||||
@@ -55,8 +55,7 @@ function compareAccessLists(list1: string[], list2: string[]): boolean {
|
|||||||
|
|
||||||
export async function connect(): Promise<void> {
|
export async function connect(): Promise<void> {
|
||||||
clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), {
|
clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true
|
||||||
useUnifiedTopology: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client = await clientPromise;
|
client = await clientPromise;
|
||||||
@@ -84,7 +83,7 @@ export async function disconnect(): Promise<void> {
|
|||||||
|
|
||||||
// Optimize projection by removing overlaps
|
// Optimize projection by removing overlaps
|
||||||
// This can modify the object
|
// This can modify the object
|
||||||
function optimizeProjection(obj: { [path: string]: 1 }): { [path: string]: 1 } {
|
function optimizeProjection(obj: {}): {} {
|
||||||
if (obj[""]) return { "": obj[""] };
|
if (obj[""]) return { "": obj[""] };
|
||||||
|
|
||||||
const keys = Object.keys(obj).sort();
|
const keys = Object.keys(obj).sort();
|
||||||
@@ -111,13 +110,13 @@ export async function fetchDevice(
|
|||||||
[
|
[
|
||||||
Path.parse("Events"),
|
Path.parse("Events"),
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1], writable: [timestamp, 0] },
|
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Path.parse("DeviceID"),
|
Path.parse("DeviceID"),
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1], writable: [timestamp, 0] },
|
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||||
],
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
const device = await devicesCollection.findOne({ _id: id });
|
const device = await devicesCollection.findOne({ _id: id });
|
||||||
@@ -147,7 +146,7 @@ export async function fetchDevice(
|
|||||||
if (obj["_notification"] != null) {
|
if (obj["_notification"] != null) {
|
||||||
attrs.notification = [
|
attrs.notification = [
|
||||||
obj["_attributesTimestamp"] || 1,
|
obj["_attributesTimestamp"] || 1,
|
||||||
obj["_notification"],
|
obj["_notification"]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,8 +175,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [+v, 0],
|
object: [+v, 0],
|
||||||
writable: [+v, 0],
|
writable: [+v, 0],
|
||||||
value: [+v, [+v, "xsd:dateTime"]],
|
value: [+v, [+v, "xsd:dateTime"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "_lastBoot":
|
case "_lastBoot":
|
||||||
@@ -187,8 +186,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [+v, 0],
|
object: [+v, 0],
|
||||||
writable: [+v, 0],
|
writable: [+v, 0],
|
||||||
value: [+v, [+v, "xsd:dateTime"]],
|
value: [+v, [+v, "xsd:dateTime"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "_lastBootstrap":
|
case "_lastBootstrap":
|
||||||
@@ -198,8 +197,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [+v, 0],
|
object: [+v, 0],
|
||||||
writable: [+v, 0],
|
writable: [+v, 0],
|
||||||
value: [+v, [+v, "xsd:dateTime"]],
|
value: [+v, [+v, "xsd:dateTime"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "_registered":
|
case "_registered":
|
||||||
@@ -210,8 +209,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [+v, "xsd:dateTime"]],
|
value: [timestamp, [+v, "xsd:dateTime"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "_id":
|
case "_id":
|
||||||
@@ -221,8 +220,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [v as string, "xsd:string"]],
|
value: [timestamp, [v as string, "xsd:string"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "_tags":
|
case "_tags":
|
||||||
@@ -230,7 +229,7 @@ export async function fetchDevice(
|
|||||||
res.push([
|
res.push([
|
||||||
Path.parse("Tags"),
|
Path.parse("Tags"),
|
||||||
timestamp,
|
timestamp,
|
||||||
{ object: [timestamp, 1], writable: [timestamp, 0] },
|
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +241,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 1],
|
writable: [timestamp, 1],
|
||||||
value: [timestamp, [true, "xsd:boolean"]],
|
value: [timestamp, [true, "xsd:boolean"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -255,8 +254,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [v["_Manufacturer"], "xsd:string"]],
|
value: [timestamp, [v["_Manufacturer"], "xsd:string"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +266,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [v["_OUI"], "xsd:string"]],
|
value: [timestamp, [v["_OUI"], "xsd:string"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,8 +278,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [v["_ProductClass"], "xsd:string"]],
|
value: [timestamp, [v["_ProductClass"], "xsd:string"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,8 +290,8 @@ export async function fetchDevice(
|
|||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
writable: [timestamp, 0],
|
writable: [timestamp, 0],
|
||||||
value: [timestamp, [v["_SerialNumber"], "xsd:string"]],
|
value: [timestamp, [v["_SerialNumber"], "xsd:string"]]
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +422,7 @@ export async function saveDevice(
|
|||||||
} else {
|
} else {
|
||||||
if (!update["$pull"]["_tags"]) {
|
if (!update["$pull"]["_tags"]) {
|
||||||
update["$pull"]["_tags"] = {
|
update["$pull"]["_tags"] = {
|
||||||
$in: [],
|
$in: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
update["$pull"]["_tags"]["$in"].push(path.segments[1]);
|
update["$pull"]["_tags"]["$in"].push(path.segments[1]);
|
||||||
@@ -581,7 +580,7 @@ export async function saveDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await devicesCollection.updateOne({ _id: deviceId }, update, {
|
const result = await devicesCollection.updateOne({ _id: deviceId }, update, {
|
||||||
upsert: isNew,
|
upsert: isNew
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.result.n !== 1)
|
if (result.result.n !== 1)
|
||||||
@@ -594,7 +593,7 @@ export async function saveDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getFaults(
|
export async function getFaults(
|
||||||
deviceId: string
|
deviceId
|
||||||
): Promise<{ [channel: string]: SessionFault }> {
|
): Promise<{ [channel: string]: SessionFault }> {
|
||||||
const res = await faultsCollection
|
const res = await faultsCollection
|
||||||
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
||||||
@@ -614,33 +613,26 @@ export async function getFaults(
|
|||||||
return faults;
|
return faults;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveFault(
|
export async function saveFault(deviceId, channel, fault): Promise<void> {
|
||||||
deviceId: string,
|
|
||||||
channel: string,
|
|
||||||
fault: SessionFault
|
|
||||||
): Promise<void> {
|
|
||||||
const id = `${deviceId}:${channel}`;
|
const id = `${deviceId}:${channel}`;
|
||||||
const f = Object.assign({}, fault) as Record<string, any>;
|
fault = Object.assign({}, fault);
|
||||||
f["_id"] = id;
|
fault._id = id;
|
||||||
f["device"] = deviceId;
|
fault.device = deviceId;
|
||||||
f["channel"] = channel;
|
fault.channel = channel;
|
||||||
f["timestamp"] = new Date(fault.timestamp);
|
fault.timestamp = new Date(fault.timestamp);
|
||||||
f["provisions"] = JSON.stringify(fault.provisions);
|
fault.provisions = JSON.stringify(fault.provisions);
|
||||||
await faultsCollection.replaceOne({ _id: id }, f, { upsert: true });
|
await faultsCollection.replaceOne({ _id: id }, fault, { upsert: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFault(
|
export async function deleteFault(deviceId, channel): Promise<void> {
|
||||||
deviceId: string,
|
|
||||||
channel: string
|
|
||||||
): Promise<void> {
|
|
||||||
await faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` });
|
await faultsCollection.deleteOne({ _id: `${deviceId}:${channel}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDueTasks(
|
export async function getDueTasks(
|
||||||
deviceId: string,
|
deviceId,
|
||||||
timestamp: number
|
timestamp
|
||||||
): Promise<[Task[], number]> {
|
): Promise<[Task[], number]> {
|
||||||
const cur = tasksCollection.find({ device: deviceId }).sort({ timestamp: 1 });
|
const cur = tasksCollection.find({ device: deviceId }).sort(["timestamp"]);
|
||||||
const tasks = [] as Task[];
|
const tasks = [] as Task[];
|
||||||
|
|
||||||
for await (const task of cur) {
|
for await (const task of cur) {
|
||||||
@@ -671,17 +663,14 @@ export async function getDueTasks(
|
|||||||
return [tasks, null];
|
return [tasks, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearTasks(
|
export async function clearTasks(deviceId, taskIds): Promise<void> {
|
||||||
deviceId: string,
|
|
||||||
taskIds: string[]
|
|
||||||
): Promise<void> {
|
|
||||||
await tasksCollection.deleteMany({
|
await tasksCollection.deleteMany({
|
||||||
_id: { $in: taskIds.map((id) => new ObjectID(id)) },
|
_id: { $in: taskIds.map(id => new ObjectID(id)) }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOperations(
|
export async function getOperations(
|
||||||
deviceId: string
|
deviceId
|
||||||
): Promise<{ [commandKey: string]: Operation }> {
|
): Promise<{ [commandKey: string]: Operation }> {
|
||||||
const res = await operationsCollection
|
const res = await operationsCollection
|
||||||
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
|
||||||
@@ -701,46 +690,43 @@ export async function getOperations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function saveOperation(
|
export async function saveOperation(
|
||||||
deviceId: string,
|
deviceId,
|
||||||
commandKey: string,
|
commandKey,
|
||||||
operation: Operation
|
operation
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const id = `${deviceId}:${commandKey}`;
|
const id = `${deviceId}:${commandKey}`;
|
||||||
const o = Object.assign({}, operation) as Record<string, any>;
|
operation = Object.assign({}, operation);
|
||||||
o["_id"] = id;
|
operation._id = id;
|
||||||
o["timestamp"] = new Date(operation.timestamp);
|
operation.timestamp = new Date(operation.timestamp);
|
||||||
o["provisions"] = JSON.stringify(operation.provisions);
|
operation.provisions = JSON.stringify(operation.provisions);
|
||||||
o["retries"] = JSON.stringify(operation.retries);
|
operation.retries = JSON.stringify(operation.retries);
|
||||||
o["args"] = JSON.stringify(operation.args);
|
operation.args = JSON.stringify(operation.args);
|
||||||
await operationsCollection.replaceOne({ _id: id }, operation, {
|
await operationsCollection.replaceOne({ _id: id }, operation, {
|
||||||
upsert: true,
|
upsert: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteOperation(
|
export async function deleteOperation(deviceId, commandKey): Promise<void> {
|
||||||
deviceId: string,
|
|
||||||
commandKey: string
|
|
||||||
): Promise<void> {
|
|
||||||
await operationsCollection.deleteOne({ _id: `${deviceId}:${commandKey}` });
|
await operationsCollection.deleteOne({ _id: `${deviceId}:${commandKey}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPresets(): Promise<Record<string, any>[]> {
|
export async function getPresets(): Promise<{}[]> {
|
||||||
return presetsCollection.find().toArray();
|
return presetsCollection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getObjects(): Promise<Record<string, any>[]> {
|
export async function getObjects(): Promise<{}[]> {
|
||||||
return objectsCollection.find().toArray();
|
return objectsCollection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProvisions(): Promise<Record<string, any>[]> {
|
export async function getProvisions(): Promise<{}[]> {
|
||||||
return provisionsCollection.find().toArray();
|
return provisionsCollection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVirtualParameters(): Promise<Record<string, any>[]> {
|
export async function getVirtualParameters(): Promise<{}[]> {
|
||||||
return virtualParametersCollection.find().toArray();
|
return virtualParametersCollection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFiles(): Promise<Record<string, any>[]> {
|
export function getFiles(): Promise<{}[]> {
|
||||||
return filesCollection.find().toArray();
|
return filesCollection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,9 +734,9 @@ export async function getConfig(): Promise<
|
|||||||
{ id: string; value: Expression }[]
|
{ id: string; value: Expression }[]
|
||||||
> {
|
> {
|
||||||
const res = await configCollection.find().toArray();
|
const res = await configCollection.find().toArray();
|
||||||
return res.map((c) => ({
|
return res.map(c => ({
|
||||||
id: c["_id"],
|
id: c["_id"],
|
||||||
value: parse(c["value"]),
|
value: parse(c["value"])
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -21,7 +21,7 @@ import {
|
|||||||
IncomingMessage,
|
IncomingMessage,
|
||||||
ServerResponse,
|
ServerResponse,
|
||||||
ClientRequest,
|
ClientRequest,
|
||||||
RequestOptions,
|
RequestOptions
|
||||||
} from "http";
|
} from "http";
|
||||||
import { Socket } from "net";
|
import { Socket } from "net";
|
||||||
import { appendFileSync } from "fs";
|
import { appendFileSync } from "fs";
|
||||||
@@ -60,7 +60,7 @@ export function incomingHttpRequest(
|
|||||||
method: httpRequest.method,
|
method: httpRequest.method,
|
||||||
url: httpRequest.url,
|
url: httpRequest.url,
|
||||||
headers: httpRequest.headers,
|
headers: httpRequest.headers,
|
||||||
body: body,
|
body: body
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
@@ -86,7 +86,7 @@ export function outgoingHttpResponse(
|
|||||||
connection: getConnectionTimestamp(con),
|
connection: getConnectionTimestamp(con),
|
||||||
statusCode: httpResponse.statusCode,
|
statusCode: httpResponse.statusCode,
|
||||||
headers: httpResponse.getHeaders(),
|
headers: httpResponse.getHeaders(),
|
||||||
body: body,
|
body: body
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
@@ -115,7 +115,7 @@ export function outgoingHttpRequest(
|
|||||||
method: options.method || "GET",
|
method: options.method || "GET",
|
||||||
url: options.path,
|
url: options.path,
|
||||||
headers: httpRequest.getHeaders(),
|
headers: httpRequest.getHeaders(),
|
||||||
body: body,
|
body: body
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
@@ -143,7 +143,7 @@ export function outgoingHttpRequestError(
|
|||||||
method: options.method,
|
method: options.method,
|
||||||
url: options.path,
|
url: options.path,
|
||||||
headers: httpRequest.getHeaders(),
|
headers: httpRequest.getHeaders(),
|
||||||
error: err.message,
|
error: err.message
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
@@ -169,7 +169,7 @@ export function incomingHttpResponse(
|
|||||||
connection: getConnectionTimestamp(httpResponse.connection),
|
connection: getConnectionTimestamp(httpResponse.connection),
|
||||||
statusCode: httpResponse.statusCode,
|
statusCode: httpResponse.statusCode,
|
||||||
headers: httpResponse.headers,
|
headers: httpResponse.headers,
|
||||||
body: body,
|
body: body
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
@@ -193,7 +193,7 @@ export function outgoingUdpMessage(
|
|||||||
remoteAddress: remoteAddress,
|
remoteAddress: remoteAddress,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
remotePort: remotePort,
|
remotePort: remotePort,
|
||||||
body: body,
|
body: body
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG_FORMAT === "yaml")
|
if (DEBUG_FORMAT === "yaml")
|
||||||
|
|||||||
+26
-51
@@ -21,15 +21,10 @@ import Path from "./common/path";
|
|||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as device from "./device";
|
import * as device from "./device";
|
||||||
import * as scheduling from "./scheduling";
|
import * as scheduling from "./scheduling";
|
||||||
import { SessionContext, Declaration } from "./types";
|
|
||||||
|
|
||||||
const MAX_DEPTH = +config.get("MAX_DEPTH");
|
const MAX_DEPTH = +config.get("MAX_DEPTH");
|
||||||
|
|
||||||
export function refresh(
|
export function refresh(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (
|
if (
|
||||||
(provision.length !== 2 || typeof provision[1] !== "string") &&
|
(provision.length !== 2 || typeof provision[1] !== "string") &&
|
||||||
(provision.length !== 3 ||
|
(provision.length !== 3 ||
|
||||||
@@ -42,7 +37,7 @@ export function refresh(
|
|||||||
)
|
)
|
||||||
throw new Error("Invalid arguments");
|
throw new Error("Invalid arguments");
|
||||||
|
|
||||||
const every = 1000 * ((provision[2] as number) || 1);
|
const every = 1000 * (provision[2] || 1);
|
||||||
const offset = scheduling.variance(sessionContext.deviceId, every);
|
const offset = scheduling.variance(sessionContext.deviceId, every);
|
||||||
const t = scheduling.interval(sessionContext.timestamp, every, offset);
|
const t = scheduling.interval(sessionContext.timestamp, every, offset);
|
||||||
|
|
||||||
@@ -54,7 +49,7 @@ export function refresh(
|
|||||||
} else {
|
} else {
|
||||||
attrGet = {};
|
attrGet = {};
|
||||||
refreshChildren = !!provision[3];
|
refreshChildren = !!provision[3];
|
||||||
for (const a of provision.slice(4)) attrGet[a as string] = t;
|
for (const a of provision.slice(4)) attrGet[a] = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = Path.parse(provision[1]);
|
let path = Path.parse(provision[1]);
|
||||||
@@ -74,18 +69,14 @@ export function refresh(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: attrGet,
|
attrGet: attrGet,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function value(
|
export function value(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (
|
if (
|
||||||
provision.length < 3 ||
|
provision.length < 3 ||
|
||||||
provision.length > 4 ||
|
provision.length > 4 ||
|
||||||
@@ -99,15 +90,15 @@ export function value(
|
|||||||
attr = "value";
|
attr = "value";
|
||||||
val = provision[2];
|
val = provision[2];
|
||||||
} else {
|
} else {
|
||||||
attr = (provision[2] as string) || "";
|
attr = provision[2] || "";
|
||||||
val = provision[3];
|
val = provision[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr === "accessList") {
|
if (attr === "accessList") {
|
||||||
val = (val || "")
|
val = (val || "")
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((s) => s.trim())
|
.map(s => s.trim())
|
||||||
.filter((s) => !!s);
|
.filter(s => !!s);
|
||||||
} else if (attr === "value") {
|
} else if (attr === "value") {
|
||||||
val = [val];
|
val = [val];
|
||||||
}
|
}
|
||||||
@@ -118,17 +109,13 @@ export function value(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { [attr]: 1 },
|
attrGet: { [attr]: 1 },
|
||||||
attrSet: { [attr]: val },
|
attrSet: { [attr]: val },
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tag(
|
export function tag(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (
|
if (
|
||||||
provision.length !== 3 ||
|
provision.length !== 3 ||
|
||||||
typeof provision[1] !== "string" ||
|
typeof provision[1] !== "string" ||
|
||||||
@@ -142,17 +129,13 @@ export function tag(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { value: 1 },
|
attrGet: { value: 1 },
|
||||||
attrSet: { value: [provision[2]] },
|
attrSet: { value: [provision[2]] },
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reboot(
|
export function reboot(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (provision.length !== 1) throw new Error("Invalid arguments");
|
if (provision.length !== 1) throw new Error("Invalid arguments");
|
||||||
|
|
||||||
declarations.push({
|
declarations.push({
|
||||||
@@ -161,17 +144,13 @@ export function reboot(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { value: 1 },
|
attrGet: { value: 1 },
|
||||||
attrSet: { value: [sessionContext.timestamp] },
|
attrSet: { value: [sessionContext.timestamp] },
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reset(
|
export function reset(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (provision.length !== 1) throw new Error("Invalid arguments");
|
if (provision.length !== 1) throw new Error("Invalid arguments");
|
||||||
|
|
||||||
declarations.push({
|
declarations.push({
|
||||||
@@ -180,17 +159,13 @@ export function reset(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { value: 1 },
|
attrGet: { value: 1 },
|
||||||
attrSet: { value: [sessionContext.timestamp] },
|
attrSet: { value: [sessionContext.timestamp] },
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(
|
export function download(sessionContext, provision, declarations): boolean {
|
||||||
sessionContext: SessionContext,
|
|
||||||
provision: (string | number | boolean)[],
|
|
||||||
declarations: Declaration[]
|
|
||||||
): boolean {
|
|
||||||
if (
|
if (
|
||||||
(provision.length !== 3 ||
|
(provision.length !== 3 ||
|
||||||
typeof provision[1] !== "string" ||
|
typeof provision[1] !== "string" ||
|
||||||
@@ -205,7 +180,7 @@ export function download(
|
|||||||
const alias = [
|
const alias = [
|
||||||
`FileType:${JSON.stringify(provision[1] || "")}`,
|
`FileType:${JSON.stringify(provision[1] || "")}`,
|
||||||
`FileName:${JSON.stringify(provision[2] || "")}`,
|
`FileName:${JSON.stringify(provision[2] || "")}`,
|
||||||
`TargetFileName:${JSON.stringify(provision[3] || "")}`,
|
`TargetFileName:${JSON.stringify(provision[3] || "")}`
|
||||||
].join(",");
|
].join(",");
|
||||||
|
|
||||||
declarations.push({
|
declarations.push({
|
||||||
@@ -214,7 +189,7 @@ export function download(
|
|||||||
pathSet: 1,
|
pathSet: 1,
|
||||||
attrGet: null,
|
attrGet: null,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
declarations.push({
|
declarations.push({
|
||||||
@@ -223,18 +198,18 @@ export function download(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: { value: 1 },
|
attrGet: { value: 1 },
|
||||||
attrSet: { value: [sessionContext.timestamp] },
|
attrSet: { value: [sessionContext.timestamp] },
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function instances(
|
export function instances(
|
||||||
sessionContext: SessionContext,
|
sessionContext,
|
||||||
provision: (string | number | boolean)[],
|
provision,
|
||||||
declarations: Declaration[],
|
declarations,
|
||||||
startRevision: number,
|
startRevision,
|
||||||
endRevision: number
|
endRevision
|
||||||
): boolean {
|
): boolean {
|
||||||
if (provision.length !== 3 || typeof provision[1] !== "string")
|
if (provision.length !== 3 || typeof provision[1] !== "string")
|
||||||
throw new Error("Invalid arguments");
|
throw new Error("Invalid arguments");
|
||||||
@@ -252,7 +227,7 @@ export function instances(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: null,
|
attrGet: null,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (endRevision === startRevision) return false;
|
if (endRevision === startRevision) return false;
|
||||||
@@ -271,7 +246,7 @@ export function instances(
|
|||||||
pathSet: count,
|
pathSet: count,
|
||||||
attrGet: null,
|
attrGet: null,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+15
-24
@@ -18,20 +18,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Path from "./common/path";
|
import Path from "./common/path";
|
||||||
import {
|
import { DeviceData, Declaration, Clear } from "./types";
|
||||||
DeviceData,
|
|
||||||
Declaration,
|
|
||||||
Clear,
|
|
||||||
AttributeTimestamps,
|
|
||||||
Attributes,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
const CHANGE_FLAGS = {
|
const CHANGE_FLAGS = {
|
||||||
object: 2,
|
object: 2,
|
||||||
writable: 4,
|
writable: 4,
|
||||||
value: 8,
|
value: 8,
|
||||||
notification: 16,
|
notification: 16,
|
||||||
accessList: 32,
|
accessList: 32
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseBool(v): boolean {
|
function parseBool(v): boolean {
|
||||||
@@ -44,7 +38,7 @@ function parseBool(v): boolean {
|
|||||||
|
|
||||||
export function sanitizeParameterValue(
|
export function sanitizeParameterValue(
|
||||||
parameterValue: [string | number | boolean, string]
|
parameterValue: [string | number | boolean, string]
|
||||||
): [string | number | boolean, string] {
|
): [string | number | boolean, string?] {
|
||||||
if (parameterValue[0] != null) {
|
if (parameterValue[0] != null) {
|
||||||
switch (parameterValue[1]) {
|
switch (parameterValue[1]) {
|
||||||
case "xsd:boolean":
|
case "xsd:boolean":
|
||||||
@@ -104,8 +98,8 @@ export function getAliasDeclarations(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: attrGet,
|
attrGet: attrGet,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (path.alias) {
|
if (path.alias) {
|
||||||
@@ -115,7 +109,7 @@ export function getAliasDeclarations(
|
|||||||
for (const [p] of alias as [Path, string][]) {
|
for (const [p] of alias as [Path, string][]) {
|
||||||
decs = decs.concat(
|
decs = decs.concat(
|
||||||
getAliasDeclarations(parent.concat(p), timestamp, {
|
getAliasDeclarations(parent.concat(p), timestamp, {
|
||||||
value: timestamp,
|
value: timestamp
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -197,10 +191,10 @@ export function unpack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function clear(
|
export function clear(
|
||||||
deviceData: DeviceData,
|
deviceData,
|
||||||
path: Path,
|
path,
|
||||||
timestamp: number,
|
timestamp,
|
||||||
attributes: AttributeTimestamps,
|
attributes,
|
||||||
changeFlags = 0
|
changeFlags = 0
|
||||||
): void {
|
): void {
|
||||||
const changeTrackers = {};
|
const changeTrackers = {};
|
||||||
@@ -279,8 +273,8 @@ function compareEquality(a, b): boolean {
|
|||||||
export function set(
|
export function set(
|
||||||
deviceData: DeviceData,
|
deviceData: DeviceData,
|
||||||
path: Path,
|
path: Path,
|
||||||
timestamp: number,
|
timestamp,
|
||||||
attributes: Attributes,
|
attributes,
|
||||||
toClear?: Clear[]
|
toClear?: Clear[]
|
||||||
): Clear[] {
|
): Clear[] {
|
||||||
path = deviceData.paths.add(path);
|
path = deviceData.paths.add(path);
|
||||||
@@ -308,7 +302,7 @@ export function set(
|
|||||||
attributes.object[1] &&
|
attributes.object[1] &&
|
||||||
attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)
|
attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)
|
||||||
)
|
)
|
||||||
attributes.value = [attributes.object[0], null];
|
attributes.value = [attributes.object[0]];
|
||||||
|
|
||||||
const newAttributes = Object.assign({}, currentAttributes, attributes);
|
const newAttributes = Object.assign({}, currentAttributes, attributes);
|
||||||
|
|
||||||
@@ -384,7 +378,7 @@ export function track(
|
|||||||
deviceData: DeviceData,
|
deviceData: DeviceData,
|
||||||
path: Path,
|
path: Path,
|
||||||
marker: string,
|
marker: string,
|
||||||
attributes?: string[]
|
attributes?
|
||||||
): void {
|
): void {
|
||||||
path = deviceData.paths.add(path);
|
path = deviceData.paths.add(path);
|
||||||
let f = 1;
|
let f = 1;
|
||||||
@@ -401,10 +395,7 @@ export function track(
|
|||||||
cur[marker] |= f;
|
cur[marker] |= f;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearTrackers(
|
export function clearTrackers(deviceData: DeviceData, tracker): void {
|
||||||
deviceData: DeviceData,
|
|
||||||
tracker: string | string[]
|
|
||||||
): void {
|
|
||||||
if (Array.isArray(tracker)) {
|
if (Array.isArray(tracker)) {
|
||||||
for (const v of deviceData.trackers.values())
|
for (const v of deviceData.trackers.values())
|
||||||
for (const t of tracker) delete v[t];
|
for (const t of tracker) delete v[t];
|
||||||
|
|||||||
+7
-8
@@ -36,8 +36,8 @@ function messageHandler(message): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function run(args: string[]): Promise<{ fault: Fault; value: any }> {
|
export function run(args): Promise<{ fault: Fault; value: any }> {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const scriptName = args[0];
|
const scriptName = args[0];
|
||||||
|
|
||||||
const id = crypto.randomBytes(8).toString("hex");
|
const id = crypto.randomBytes(8).toString("hex");
|
||||||
@@ -45,20 +45,19 @@ export function run(args: string[]): Promise<{ fault: Fault; value: any }> {
|
|||||||
|
|
||||||
if (!processes[scriptName]) {
|
if (!processes[scriptName]) {
|
||||||
const p = spawn(ROOT_DIR + "/bin/genieacs-ext", [scriptName], {
|
const p = spawn(ROOT_DIR + "/bin/genieacs-ext", [scriptName], {
|
||||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
stdio: ["inherit", "inherit", "inherit", "ipc"]
|
||||||
});
|
});
|
||||||
processes[scriptName] = p;
|
processes[scriptName] = p;
|
||||||
|
|
||||||
p.on("error", (err) => {
|
p.on("error", err => {
|
||||||
if (processes[scriptName] === p) {
|
if (processes[scriptName] === p) {
|
||||||
if (jobs.delete(id)) {
|
if (jobs.delete(id)) {
|
||||||
resolve({
|
resolve({
|
||||||
fault: { code: err.name, message: err.message },
|
fault: { code: err.name, message: err.message },
|
||||||
value: null,
|
value: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
kill(processes[scriptName]);
|
kill(processes[scriptName]);
|
||||||
delete processes[scriptName];
|
delete processes[scriptName];
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,7 @@ export function run(args: string[]): Promise<{ fault: Fault; value: any }> {
|
|||||||
if (jobs.delete(id)) {
|
if (jobs.delete(id)) {
|
||||||
resolve({
|
resolve({
|
||||||
fault: { code: "timeout", message: "Extension timed out" },
|
fault: { code: "timeout", message: "Extension timed out" },
|
||||||
value: null,
|
value: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
@@ -87,7 +86,7 @@ export function run(args: string[]): Promise<{ fault: Fault; value: any }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function kill(process: ChildProcess): Promise<void> {
|
function kill(process: ChildProcess): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const timeToKill = Date.now() + 5000;
|
const timeToKill = Date.now() + 5000;
|
||||||
|
|
||||||
process.kill();
|
process.kill();
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
|
||||||
*
|
|
||||||
* This file is part of GenieACS.
|
|
||||||
*
|
|
||||||
* GenieACS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* GenieACS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { IncomingMessage } from "http";
|
|
||||||
import { TLSSocket } from "tls";
|
|
||||||
import { parseCIDR, parse, IPv6, IPv4 } from "ipaddr.js";
|
|
||||||
import * as config from "./config";
|
|
||||||
|
|
||||||
interface RequestOrigin {
|
|
||||||
localAddress: string;
|
|
||||||
localPort: number;
|
|
||||||
remoteAddress: string;
|
|
||||||
remotePort: number;
|
|
||||||
host: string;
|
|
||||||
encrypted: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FORWARDED_HEADER = "" + config.get("FORWARDED_HEADER");
|
|
||||||
const cache = new WeakMap<IncomingMessage, RequestOrigin>();
|
|
||||||
const cidrs: [IPv4 | IPv6, number][] = [];
|
|
||||||
|
|
||||||
for (const str of FORWARDED_HEADER.split(",").map((s) => s.trim())) {
|
|
||||||
try {
|
|
||||||
cidrs.push(parseCIDR(str));
|
|
||||||
} catch (err) {
|
|
||||||
// Not a valid CIDR format, try parsing as IP
|
|
||||||
try {
|
|
||||||
const ip = parse(str);
|
|
||||||
cidrs.push([ip, ip.toByteArray().length * 8]);
|
|
||||||
} catch (err) {
|
|
||||||
// Not a valid IP either, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseForwardedHeader(str: string): { [name: string]: string } {
|
|
||||||
str = str.toLowerCase();
|
|
||||||
const res: { [name: string]: string } = {};
|
|
||||||
let keyIdx = 0;
|
|
||||||
let valueIdx = -1;
|
|
||||||
let key: string;
|
|
||||||
for (let i = 0; i < str.length; ++i) {
|
|
||||||
const char = str.charCodeAt(i);
|
|
||||||
if (char === 61 /* = */) {
|
|
||||||
if (keyIdx >= 0) {
|
|
||||||
key = str.slice(keyIdx, i).trim();
|
|
||||||
keyIdx = -1;
|
|
||||||
valueIdx = i + 1;
|
|
||||||
}
|
|
||||||
} else if (char === 59 /* ; */) {
|
|
||||||
if (valueIdx >= 0) res[key] = str.slice(valueIdx, i).trim();
|
|
||||||
valueIdx = -1;
|
|
||||||
keyIdx = i + 1;
|
|
||||||
} else if (char === 44 /* , */) {
|
|
||||||
if (valueIdx >= 0) res[key] = str.slice(valueIdx, i).trim();
|
|
||||||
return res;
|
|
||||||
} else if (char === 34 /* " */) {
|
|
||||||
if (valueIdx >= 0) {
|
|
||||||
const quoteIdx = i;
|
|
||||||
if (!str.slice(valueIdx, quoteIdx).trim()) {
|
|
||||||
for (i = i + 1; i < str.length; ++i) {
|
|
||||||
const c = str.charCodeAt(i);
|
|
||||||
if (c === 92 /* \ */) ++i;
|
|
||||||
if (c === 34 /* " */) {
|
|
||||||
res[key] = JSON.parse(str.slice(quoteIdx, i + 1).trim());
|
|
||||||
valueIdx = -1;
|
|
||||||
keyIdx = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueIdx >= 0) res[key] = str.slice(valueIdx).trim();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRequestOrigin(request: IncomingMessage): RequestOrigin {
|
|
||||||
let origin = cache.get(request);
|
|
||||||
if (!origin) {
|
|
||||||
const soc = request.socket;
|
|
||||||
origin = {
|
|
||||||
localAddress: soc.localAddress,
|
|
||||||
localPort: soc.localPort,
|
|
||||||
remoteAddress: soc.remoteAddress,
|
|
||||||
remotePort: soc.remotePort,
|
|
||||||
host: request.headers["host"],
|
|
||||||
encrypted: !!(request.socket as TLSSocket).encrypted,
|
|
||||||
};
|
|
||||||
|
|
||||||
const header = request.headers["forwarded"];
|
|
||||||
if (header) {
|
|
||||||
const ip = parse(soc.remoteAddress) as IPv4;
|
|
||||||
if (cidrs.some((cidr) => ip.match(cidr as [IPv4, number]))) {
|
|
||||||
const parsed = parseForwardedHeader(header);
|
|
||||||
|
|
||||||
if (parsed["proto"] === "https") {
|
|
||||||
origin.encrypted = true;
|
|
||||||
origin.localPort = 443;
|
|
||||||
} else if (parsed["proto"] === "http") {
|
|
||||||
origin.encrypted = false;
|
|
||||||
origin.localPort = 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed["host"]) {
|
|
||||||
origin.host = parsed["host"];
|
|
||||||
const [, port] = parsed["host"].split(":", 2);
|
|
||||||
origin.localPort = +port || origin.localPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed["for"]) {
|
|
||||||
if (parsed["for"].startsWith("[")) {
|
|
||||||
const i = parsed["for"].lastIndexOf("]");
|
|
||||||
if (i >= 0) {
|
|
||||||
origin.remoteAddress = parsed["for"].slice(1, i);
|
|
||||||
origin.remotePort =
|
|
||||||
parseInt(parsed["for"].slice(i + 2)) || origin.remotePort;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const i = parsed["for"].lastIndexOf(":");
|
|
||||||
if (i >= 0) {
|
|
||||||
origin.remoteAddress = parsed["for"].slice(0, i);
|
|
||||||
origin.remotePort =
|
|
||||||
parseInt(parsed["for"].slice(i + 1)) || origin.remotePort;
|
|
||||||
} else {
|
|
||||||
origin.remoteAddress = parsed["for"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed["by"]) {
|
|
||||||
if (parsed["by"].startsWith("[")) {
|
|
||||||
const i = parsed["by"].lastIndexOf("]");
|
|
||||||
if (i >= 0) {
|
|
||||||
origin.localAddress = parsed["by"].slice(1, i);
|
|
||||||
origin.localPort =
|
|
||||||
parseInt(parsed["by"].slice(i + 2)) || origin.localPort;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const i = parsed["by"].lastIndexOf(":");
|
|
||||||
if (i >= 0) {
|
|
||||||
origin.localAddress = parsed["by"].slice(0, i);
|
|
||||||
origin.localPort =
|
|
||||||
parseInt(parsed["by"].slice(i + 1)) || origin.localPort;
|
|
||||||
} else {
|
|
||||||
origin.localAddress = parsed["by"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.set(request, origin);
|
|
||||||
}
|
|
||||||
return origin;
|
|
||||||
}
|
|
||||||
@@ -19,16 +19,11 @@
|
|||||||
|
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
import * as querystring from "querystring";
|
import * as querystring from "querystring";
|
||||||
import { IncomingMessage, ServerResponse } from "http";
|
|
||||||
import { GridFSBucket } from "mongodb";
|
import { GridFSBucket } from "mongodb";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import { getRequestOrigin } from "./forwarded";
|
|
||||||
|
|
||||||
export function listener(
|
export function listener(request, response): void {
|
||||||
request: IncomingMessage,
|
|
||||||
response: ServerResponse
|
|
||||||
): 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));
|
||||||
@@ -36,7 +31,7 @@ export function listener(
|
|||||||
const log = {
|
const log = {
|
||||||
message: "Fetch file",
|
message: "Fetch file",
|
||||||
filename: filename,
|
filename: filename,
|
||||||
remoteAddress: getRequestOrigin(request).remoteAddress,
|
remoteAddress: request.connection.remoteAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
db.filesCollection.findOne({ _id: filename }, (err, file) => {
|
db.filesCollection.findOne({ _id: filename }, (err, file) => {
|
||||||
@@ -52,7 +47,7 @@ export function listener(
|
|||||||
|
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
"Content-Type": file.contentType || "application/octet-stream",
|
"Content-Type": file.contentType || "application/octet-stream",
|
||||||
"Content-Length": file.length,
|
"Content-Length": file.length
|
||||||
});
|
});
|
||||||
|
|
||||||
const bucket = new GridFSBucket(db.client.db());
|
const bucket = new GridFSBucket(db.client.db());
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const UNDISCOVERED_DEPTH = 7;
|
|||||||
// point has been discovered.
|
// point has been discovered.
|
||||||
export function estimateGpnCount(
|
export function estimateGpnCount(
|
||||||
gpnPatterns: [Path, number][],
|
gpnPatterns: [Path, number][],
|
||||||
depth = 0
|
depth: number = 0
|
||||||
): number {
|
): number {
|
||||||
const children: { [segment: string]: [Path, number][] } = {};
|
const children: { [segment: string]: [Path, number][] } = {};
|
||||||
const wildcardChildren: [Path, number][] = [];
|
const wildcardChildren: [Path, number][] = [];
|
||||||
|
|||||||
+49
-50
@@ -95,11 +95,11 @@ export async function getStatus(): Promise<Status> {
|
|||||||
filters: !Object.keys(ui["filters"]).length,
|
filters: !Object.keys(ui["filters"]).length,
|
||||||
device: !Object.keys(ui["device"]).length,
|
device: !Object.keys(ui["device"]).length,
|
||||||
index: !Object.keys(ui["index"]).length,
|
index: !Object.keys(ui["index"]).length,
|
||||||
overview: !Object.keys(ui["overview"]).length,
|
overview: !Object.keys(ui["overview"]).length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function seed(options: Record<string, boolean>): Promise<void> {
|
export async function seed(options): Promise<void> {
|
||||||
const resources = {};
|
const resources = {};
|
||||||
const proms = [];
|
const proms = [];
|
||||||
|
|
||||||
@@ -117,12 +117,12 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
role: "admin",
|
role: "admin",
|
||||||
resource: "virtualParameters",
|
resource: "virtualParameters",
|
||||||
access: 3,
|
access: 3,
|
||||||
validate: "true",
|
validate: "true"
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
resources["users"] = [
|
resources["users"] = [
|
||||||
{ username: "admin", password: "admin", roles: ["admin"] },
|
{ username: "admin", password: "admin", roles: ["admin"] }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.filters.1.parameter", value: "DeviceID.ProductClass" },
|
{ _id: "ui.filters.1.parameter", value: "DeviceID.ProductClass" },
|
||||||
{ _id: "ui.filters.1.type", value: "'string'" },
|
{ _id: "ui.filters.1.type", value: "'string'" },
|
||||||
{ _id: "ui.filters.2.label", value: "'Tag'" },
|
{ _id: "ui.filters.2.label", value: "'Tag'" },
|
||||||
{ _id: "ui.filters.2.type", value: "'tag'" },
|
{ _id: "ui.filters.2.type", value: "'tag'" }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,118 +149,118 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.device.2.parameters.0.label", value: "'Last inform'" },
|
{ _id: "ui.device.2.parameters.0.label", value: "'Last inform'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.0.type",
|
_id: "ui.device.2.parameters.0.components.0.type",
|
||||||
value: "'parameter'",
|
value: "'parameter'"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.0.components.1.chart", value: "'online'" },
|
{ _id: "ui.device.2.parameters.0.components.1.chart", value: "'online'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.1.type",
|
_id: "ui.device.2.parameters.0.components.1.type",
|
||||||
value: "'overview-dot'",
|
value: "'overview-dot'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.type",
|
_id: "ui.device.2.parameters.0.components.2.type",
|
||||||
value: "'summon-button'",
|
value: "'summon-button'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.0",
|
_id: "ui.device.2.parameters.0.components.2.parameters.0",
|
||||||
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion",
|
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.1",
|
_id: "ui.device.2.parameters.0.components.2.parameters.1",
|
||||||
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
|
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.2",
|
_id: "ui.device.2.parameters.0.components.2.parameters.2",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress",
|
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.3",
|
_id: "ui.device.2.parameters.0.components.2.parameters.3",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress",
|
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.4",
|
_id: "ui.device.2.parameters.0.components.2.parameters.4",
|
||||||
value: "InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID",
|
value: "InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.5",
|
_id: "ui.device.2.parameters.0.components.2.parameters.5",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase",
|
"InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.6",
|
_id: "ui.device.2.parameters.0.components.2.parameters.6",
|
||||||
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName",
|
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.7",
|
_id: "ui.device.2.parameters.0.components.2.parameters.7",
|
||||||
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress",
|
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.components.2.parameters.8",
|
_id: "ui.device.2.parameters.0.components.2.parameters.8",
|
||||||
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress",
|
value: "InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.0.parameter",
|
_id: "ui.device.2.parameters.0.parameter",
|
||||||
value: "DATE_STRING(Events.Inform)",
|
value: "DATE_STRING(Events.Inform)"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.1.label", value: "'Serial number'" },
|
{ _id: "ui.device.2.parameters.1.label", value: "'Serial number'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.1.parameter",
|
_id: "ui.device.2.parameters.1.parameter",
|
||||||
value: "DeviceID.SerialNumber",
|
value: "DeviceID.SerialNumber"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.2.label", value: "'Product class'" },
|
{ _id: "ui.device.2.parameters.2.label", value: "'Product class'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.2.parameter",
|
_id: "ui.device.2.parameters.2.parameter",
|
||||||
value: "DeviceID.ProductClass",
|
value: "DeviceID.ProductClass"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.3.label", value: "'OUI'" },
|
{ _id: "ui.device.2.parameters.3.label", value: "'OUI'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.3.parameter",
|
_id: "ui.device.2.parameters.3.parameter",
|
||||||
value: "DeviceID.OUI",
|
value: "DeviceID.OUI"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.4.label", value: "'Manufacturer'" },
|
{ _id: "ui.device.2.parameters.4.label", value: "'Manufacturer'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.4.parameter",
|
_id: "ui.device.2.parameters.4.parameter",
|
||||||
value: "DeviceID.Manufacturer",
|
value: "DeviceID.Manufacturer"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.5.label", value: "'Hardware version'" },
|
{ _id: "ui.device.2.parameters.5.label", value: "'Hardware version'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.5.parameter",
|
_id: "ui.device.2.parameters.5.parameter",
|
||||||
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion",
|
value: "InternetGatewayDevice.DeviceInfo.HardwareVersion"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.6.label", value: "'Software version'" },
|
{ _id: "ui.device.2.parameters.6.label", value: "'Software version'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.6.parameter",
|
_id: "ui.device.2.parameters.6.parameter",
|
||||||
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
|
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.7.label", value: "'MAC'" },
|
{ _id: "ui.device.2.parameters.7.label", value: "'MAC'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.7.parameter",
|
_id: "ui.device.2.parameters.7.parameter",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress",
|
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.8.label", value: "'IP'" },
|
{ _id: "ui.device.2.parameters.8.label", value: "'IP'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.8.parameter",
|
_id: "ui.device.2.parameters.8.parameter",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
|
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.9.label", value: "'WLAN SSID'" },
|
{ _id: "ui.device.2.parameters.9.label", value: "'WLAN SSID'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.9.parameter",
|
_id: "ui.device.2.parameters.9.parameter",
|
||||||
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
|
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.2.parameters.10.label", value: "'WLAN passphrase'" },
|
{ _id: "ui.device.2.parameters.10.label", value: "'WLAN passphrase'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.2.parameters.10.parameter",
|
_id: "ui.device.2.parameters.10.parameter",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase",
|
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.3.type", value: "'parameter-table'" },
|
{ _id: "ui.device.3.type", value: "'parameter-table'" },
|
||||||
{ _id: "ui.device.3.label", value: "'WLAN hosts'" },
|
{ _id: "ui.device.3.label", value: "'WLAN hosts'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.3.parameter",
|
_id: "ui.device.3.parameter",
|
||||||
value: "InternetGatewayDevice.LANDevice.1.Hosts.Host",
|
value: "InternetGatewayDevice.LANDevice.1.Hosts.Host"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.3.childParameters.0.label", value: "'Host name'" },
|
{ _id: "ui.device.3.childParameters.0.label", value: "'Host name'" },
|
||||||
{ _id: "ui.device.3.childParameters.0.parameter", value: "HostName" },
|
{ _id: "ui.device.3.childParameters.0.parameter", value: "HostName" },
|
||||||
@@ -278,12 +278,12 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.device.5.element", value: "'div.container-full-width'" },
|
{ _id: "ui.device.5.element", value: "'div.container-full-width'" },
|
||||||
{
|
{
|
||||||
_id: "ui.device.5.components.0.components.0",
|
_id: "ui.device.5.components.0.components.0",
|
||||||
value: "'All parameters'",
|
value: "'All parameters'"
|
||||||
},
|
},
|
||||||
{ _id: "ui.device.5.components.0.element", value: "'h3'" },
|
{ _id: "ui.device.5.components.0.element", value: "'h3'" },
|
||||||
{ _id: "ui.device.5.components.0.type", value: "'container'" },
|
{ _id: "ui.device.5.components.0.type", value: "'container'" },
|
||||||
{ _id: "ui.device.5.components.1.type", value: "'all-parameters'" },
|
{ _id: "ui.device.5.components.1.type", value: "'all-parameters'" },
|
||||||
{ _id: "ui.device.6.type", value: "'device-actions'" },
|
{ _id: "ui.device.6.type", value: "'device-actions'" }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,18 +298,18 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.index.2.label", value: "'Software version'" },
|
{ _id: "ui.index.2.label", value: "'Software version'" },
|
||||||
{
|
{
|
||||||
_id: "ui.index.2.parameter",
|
_id: "ui.index.2.parameter",
|
||||||
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
|
value: "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
|
||||||
},
|
},
|
||||||
{ _id: "ui.index.3.label", value: "'IP'" },
|
{ _id: "ui.index.3.label", value: "'IP'" },
|
||||||
{
|
{
|
||||||
_id: "ui.index.3.parameter",
|
_id: "ui.index.3.parameter",
|
||||||
value:
|
value:
|
||||||
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
|
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress"
|
||||||
},
|
},
|
||||||
{ _id: "ui.index.4.label", value: "'SSID'" },
|
{ _id: "ui.index.4.label", value: "'SSID'" },
|
||||||
{
|
{
|
||||||
_id: "ui.index.4.parameter",
|
_id: "ui.index.4.parameter",
|
||||||
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
|
value: "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID"
|
||||||
},
|
},
|
||||||
{ _id: "ui.index.5.type", value: "'container'" },
|
{ _id: "ui.index.5.type", value: "'container'" },
|
||||||
{ _id: "ui.index.5.label", value: "'Last inform'" },
|
{ _id: "ui.index.5.label", value: "'Last inform'" },
|
||||||
@@ -322,7 +322,7 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.index.6.label", value: "'Tags'" },
|
{ _id: "ui.index.6.label", value: "'Tags'" },
|
||||||
{ _id: "ui.index.6.parameter", value: "Tags" },
|
{ _id: "ui.index.6.parameter", value: "Tags" },
|
||||||
{ _id: "ui.index.6.unsortable", value: "true" },
|
{ _id: "ui.index.6.unsortable", value: "true" },
|
||||||
{ _id: "ui.index.6.writable", value: "false" },
|
{ _id: "ui.index.6.writable", value: "false" }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,44 +331,43 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
{ _id: "ui.overview.charts.online.label", value: "'Online status'" },
|
{ _id: "ui.overview.charts.online.label", value: "'Online status'" },
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.1_onlineNow.color",
|
_id: "ui.overview.charts.online.slices.1_onlineNow.color",
|
||||||
value: "'#31a354'",
|
value: "'#31a354'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.1_onlineNow.filter",
|
_id: "ui.overview.charts.online.slices.1_onlineNow.filter",
|
||||||
value: "Events.Inform > NOW() - 5 * 60 * 1000",
|
value: "Events.Inform > NOW() - 5 * 60 * 1000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.1_onlineNow.label",
|
_id: "ui.overview.charts.online.slices.1_onlineNow.label",
|
||||||
value: "'Online now'",
|
value: "'Online now'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.2_past24.color",
|
_id: "ui.overview.charts.online.slices.2_past24.color",
|
||||||
value: "'#a1d99b'",
|
value: "'#a1d99b'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.2_past24.filter",
|
_id: "ui.overview.charts.online.slices.2_past24.filter",
|
||||||
value:
|
value:
|
||||||
"Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)",
|
"Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.2_past24.label",
|
_id: "ui.overview.charts.online.slices.2_past24.label",
|
||||||
value: "'Past 24 hours'",
|
value: "'Past 24 hours'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.3_others.color",
|
_id: "ui.overview.charts.online.slices.3_others.color",
|
||||||
value: "'#e5f5e0'",
|
value: "'#e5f5e0'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.3_others.filter",
|
_id: "ui.overview.charts.online.slices.3_others.filter",
|
||||||
value:
|
value: "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)"
|
||||||
"Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "ui.overview.charts.online.slices.3_others.label",
|
_id: "ui.overview.charts.online.slices.3_others.label",
|
||||||
value: "'Others'",
|
value: "'Others'"
|
||||||
},
|
},
|
||||||
{ _id: "ui.overview.groups.online.label", value: "''" },
|
{ _id: "ui.overview.groups.online.label", value: "''" },
|
||||||
{ _id: "ui.overview.groups.online.charts.0", value: "'online'" },
|
{ _id: "ui.overview.groups.online.charts.0", value: "'online'" }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,16 +378,16 @@ export async function seed(options: Record<string, boolean>): Promise<void> {
|
|||||||
weight: 0,
|
weight: 0,
|
||||||
channel: "bootstrap",
|
channel: "bootstrap",
|
||||||
events: "0 BOOTSTRAP",
|
events: "0 BOOTSTRAP",
|
||||||
provision: "bootstrap",
|
provision: "bootstrap"
|
||||||
},
|
},
|
||||||
{ _id: "default", weight: 0, channel: "default", provision: "default" },
|
{ _id: "default", weight: 0, channel: "default", provision: "default" },
|
||||||
{ _id: "inform", weight: 0, channel: "inform", provision: "inform" },
|
{ _id: "inform", weight: 0, channel: "inform", provision: "inform" }
|
||||||
];
|
];
|
||||||
|
|
||||||
resources["provisions"] = [
|
resources["provisions"] = [
|
||||||
{ _id: "bootstrap", script: BOOTSTRAP_SCRIPT },
|
{ _id: "bootstrap", script: BOOTSTRAP_SCRIPT },
|
||||||
{ _id: "default", script: DEFAULT_SCRIPT },
|
{ _id: "default", script: DEFAULT_SCRIPT },
|
||||||
{ _id: "inform", script: INFORM_SCRIPT },
|
{ _id: "inform", script: INFORM_SCRIPT }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+65
-89
@@ -22,39 +22,30 @@ 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";
|
||||||
import { convertOldPrecondition } from "./mongodb-functions";
|
import { mongoQueryToFilter } from "./mongodb-functions";
|
||||||
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 { parse } from "./common/expression-parser";
|
import { parse } from "./common/expression-parser";
|
||||||
import {
|
import { Preset, Expression } from "./types";
|
||||||
Preset,
|
|
||||||
Expression,
|
|
||||||
Provisions,
|
|
||||||
VirtualParameters,
|
|
||||||
Files,
|
|
||||||
Users,
|
|
||||||
Permissions,
|
|
||||||
Config,
|
|
||||||
UiConfig,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
interface Snapshot {
|
interface Permissions {
|
||||||
presets: Preset[];
|
[role: string]: {
|
||||||
provisions: Provisions;
|
[access: number]: {
|
||||||
virtualParameters: VirtualParameters;
|
[resource: string]: {
|
||||||
files: Files;
|
access: number;
|
||||||
permissions: Permissions;
|
filter: Expression;
|
||||||
users: Users;
|
validate?: Expression;
|
||||||
config: Config;
|
};
|
||||||
ui: UiConfig;
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const REFRESH = 3000;
|
const REFRESH = 3000;
|
||||||
const EVICT_TIMEOUT = 60000;
|
const EVICT_TIMEOUT = 60000;
|
||||||
|
|
||||||
const snapshots = new Map<string, Snapshot>();
|
const snapshots = new Map();
|
||||||
let currentSnapshot: string = null;
|
let currentSnapshot = null;
|
||||||
let nextRefresh = 1;
|
let nextRefresh = 1;
|
||||||
|
|
||||||
function computeHash(snapshot): string {
|
function computeHash(snapshot): string {
|
||||||
@@ -107,15 +98,11 @@ function computeHash(snapshot): string {
|
|||||||
return h.digest("hex");
|
return h.digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenObject(
|
function flattenObject(src, prefix = "", dst = {}): {} {
|
||||||
src: Record<string, unknown>,
|
|
||||||
prefix = "",
|
|
||||||
dst = {}
|
|
||||||
): Record<string, unknown> {
|
|
||||||
for (const k of Object.keys(src)) {
|
for (const k of Object.keys(src)) {
|
||||||
const v = src[k];
|
const v = src[k];
|
||||||
if (typeof v === "object" && !Array.isArray(v))
|
if (typeof v === "object" && !Array.isArray(v))
|
||||||
flattenObject(v as Record<string, unknown>, `${prefix}${k}.`, dst);
|
flattenObject(v, `${prefix}${k}.`, dst);
|
||||||
else dst[`${prefix}${k}`] = v;
|
else dst[`${prefix}${k}`] = v;
|
||||||
}
|
}
|
||||||
return dst;
|
return dst;
|
||||||
@@ -125,14 +112,14 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
const res = await db.getPresets();
|
const res = await db.getPresets();
|
||||||
let objects = await db.getObjects();
|
let objects = await db.getObjects();
|
||||||
|
|
||||||
objects = objects.map((obj) => {
|
objects = objects.map(obj => {
|
||||||
// Flatten object
|
// Flatten object
|
||||||
obj = flattenObject(obj);
|
obj = flattenObject(obj);
|
||||||
|
|
||||||
// If no keys are defined, consider all parameters as keys to keep the
|
// If no keys are defined, consider all parameters as keys to keep the
|
||||||
// same behavior from v1.0
|
// same behavior from v1.0
|
||||||
if (!obj["_keys"] || !obj["_keys"].length)
|
if (!obj["_keys"] || !obj["_keys"].length)
|
||||||
obj["_keys"] = Object.keys(obj).filter((k) => !k.startsWith("_"));
|
obj["_keys"] = Object.keys(obj).filter(k => !k.startsWith("_"));
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
@@ -149,9 +136,12 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
if (preset["schedule"]) {
|
if (preset["schedule"]) {
|
||||||
const parts = preset["schedule"].trim().split(/\s+/);
|
const parts = preset["schedule"].trim().split(/\s+/);
|
||||||
schedule = {
|
schedule = {
|
||||||
md5: crypto.createHash("md5").update(preset["schedule"]).digest("hex"),
|
md5: crypto
|
||||||
|
.createHash("md5")
|
||||||
|
.update(preset["schedule"])
|
||||||
|
.digest("hex"),
|
||||||
duration: null,
|
duration: null,
|
||||||
schedule: null,
|
schedule: null
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -161,7 +151,7 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
logger.warn({
|
logger.warn({
|
||||||
message: "Invalid preset schedule",
|
message: "Invalid preset schedule",
|
||||||
preset: preset["_id"],
|
preset: preset["_id"],
|
||||||
schedule: preset["schedule"],
|
schedule: preset["schedule"]
|
||||||
});
|
});
|
||||||
schedule.schedule = false;
|
schedule.schedule = false;
|
||||||
}
|
}
|
||||||
@@ -173,9 +163,7 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
try {
|
try {
|
||||||
precondition = parse(preset["precondition"]);
|
precondition = parse(preset["precondition"]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
precondition = convertOldPrecondition(
|
precondition = mongoQueryToFilter(JSON.parse(preset["precondition"]));
|
||||||
JSON.parse(preset["precondition"])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplify expression
|
// Simplify expression
|
||||||
@@ -211,7 +199,7 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
for (const obj of objects) {
|
for (const obj of objects) {
|
||||||
if (obj["_id"] === c.object) {
|
if (obj["_id"] === c.object) {
|
||||||
const alias = obj["_keys"]
|
const alias = obj["_keys"]
|
||||||
.map((k) => `${k}:${JSON.stringify(obj[k])}`)
|
.map(k => `${k}:${JSON.stringify(obj[k])}`)
|
||||||
.join(",");
|
.join(",");
|
||||||
const p = `${c.name}.[${alias}]`;
|
const p = `${c.name}.[${alias}]`;
|
||||||
_provisions.push(["instances", p, 1]);
|
_provisions.push(["instances", p, 1]);
|
||||||
@@ -229,7 +217,7 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
for (const obj of objects) {
|
for (const obj of objects) {
|
||||||
if (obj["_id"] === c.object) {
|
if (obj["_id"] === c.object) {
|
||||||
const alias = obj["_keys"]
|
const alias = obj["_keys"]
|
||||||
.map((k) => `${k}:${JSON.stringify(obj[k])}`)
|
.map(k => `${k}:${JSON.stringify(obj[k])}`)
|
||||||
.join(",");
|
.join(",");
|
||||||
const p = `${c.name}.[${alias}]`;
|
const p = `${c.name}.[${alias}]`;
|
||||||
_provisions.push(["instances", p, 0]);
|
_provisions.push(["instances", p, 0]);
|
||||||
@@ -249,14 +237,14 @@ async function fetchPresets(): Promise<Preset[]> {
|
|||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
events: events,
|
events: events,
|
||||||
precondition: precondition,
|
precondition: precondition,
|
||||||
provisions: _provisions,
|
provisions: _provisions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return presets;
|
return presets;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchProvisions(): Promise<Provisions> {
|
async function fetchProvisions(): Promise<{}> {
|
||||||
const res = await db.getProvisions();
|
const res = await db.getProvisions();
|
||||||
|
|
||||||
const provisions = {};
|
const provisions = {};
|
||||||
@@ -266,9 +254,7 @@ async function fetchProvisions(): Promise<Provisions> {
|
|||||||
.createHash("md5")
|
.createHash("md5")
|
||||||
.update(r["script"])
|
.update(r["script"])
|
||||||
.digest("hex");
|
.digest("hex");
|
||||||
provisions[
|
provisions[r["_id"]].script = new vm.Script(
|
||||||
r["_id"]
|
|
||||||
].script = new vm.Script(
|
|
||||||
`"use strict";(function(){\n${r["script"]}\n})();`,
|
`"use strict";(function(){\n${r["script"]}\n})();`,
|
||||||
{ filename: r["_id"], lineOffset: -1, timeout: 50 }
|
{ filename: r["_id"], lineOffset: -1, timeout: 50 }
|
||||||
);
|
);
|
||||||
@@ -277,7 +263,7 @@ async function fetchProvisions(): Promise<Provisions> {
|
|||||||
return provisions;
|
return provisions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchVirtualParameters(): Promise<VirtualParameters> {
|
async function fetchVirtualParameters(): Promise<{}> {
|
||||||
const res = await db.getVirtualParameters();
|
const res = await db.getVirtualParameters();
|
||||||
|
|
||||||
const virtualParameters = {};
|
const virtualParameters = {};
|
||||||
@@ -287,9 +273,7 @@ async function fetchVirtualParameters(): Promise<VirtualParameters> {
|
|||||||
.createHash("md5")
|
.createHash("md5")
|
||||||
.update(r["script"])
|
.update(r["script"])
|
||||||
.digest("hex");
|
.digest("hex");
|
||||||
virtualParameters[
|
virtualParameters[r["_id"]].script = new vm.Script(
|
||||||
r["_id"]
|
|
||||||
].script = new vm.Script(
|
|
||||||
`"use strict";(function(){\n${r["script"]}\n})();`,
|
`"use strict";(function(){\n${r["script"]}\n})();`,
|
||||||
{ filename: r["_id"], lineOffset: -1, timeout: 50 }
|
{ filename: r["_id"], lineOffset: -1, timeout: 50 }
|
||||||
);
|
);
|
||||||
@@ -308,7 +292,7 @@ async function fetchPermissions(): Promise<Permissions> {
|
|||||||
|
|
||||||
permissions[p.role][p.access][p.resource] = {
|
permissions[p.role][p.access][p.resource] = {
|
||||||
access: p.access,
|
access: p.access,
|
||||||
filter: parse(p.filter || "true"),
|
filter: parse(p.filter || "true")
|
||||||
};
|
};
|
||||||
if (p.validate)
|
if (p.validate)
|
||||||
permissions[p.role][p.access][p.resource].validate = parse(p.validate);
|
permissions[p.role][p.access][p.resource].validate = parse(p.validate);
|
||||||
@@ -317,7 +301,7 @@ async function fetchPermissions(): Promise<Permissions> {
|
|||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchFiles(): Promise<Files> {
|
async function fetchFiles(): Promise<{}> {
|
||||||
const res = await db.getFiles();
|
const res = await db.getFiles();
|
||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
@@ -332,7 +316,7 @@ async function fetchFiles(): Promise<Files> {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUsers(): Promise<Users> {
|
async function fetchUsers(): Promise<{}> {
|
||||||
const _users = await db.getUsers();
|
const _users = await db.getUsers();
|
||||||
const users = {};
|
const users = {};
|
||||||
|
|
||||||
@@ -340,14 +324,14 @@ async function fetchUsers(): Promise<Users> {
|
|||||||
users[user._id] = {
|
users[user._id] = {
|
||||||
password: user.password,
|
password: user.password,
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
roles: user.roles.split(",").map((s) => s.trim()),
|
roles: user.roles.split(",").map(s => s.trim())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchConfig(): Promise<[Config, UiConfig]> {
|
async function fetchConfig(): Promise<[{}, {}]> {
|
||||||
const conf = await db.getConfig();
|
const conf = await db.getConfig();
|
||||||
|
|
||||||
conf.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
|
conf.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
|
||||||
@@ -356,7 +340,7 @@ async function fetchConfig(): Promise<[Config, UiConfig]> {
|
|||||||
filters: {},
|
filters: {},
|
||||||
device: {},
|
device: {},
|
||||||
index: {},
|
index: {},
|
||||||
overview: {},
|
overview: {}
|
||||||
};
|
};
|
||||||
const _config = {};
|
const _config = {};
|
||||||
|
|
||||||
@@ -383,7 +367,7 @@ async function fetchConfig(): Promise<[Config, UiConfig]> {
|
|||||||
|
|
||||||
async function refresh(): Promise<void> {
|
async function refresh(): Promise<void> {
|
||||||
if (!nextRefresh) {
|
if (!nextRefresh) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
await new Promise(resolve => setTimeout(resolve, 20));
|
||||||
await refresh();
|
await refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -407,7 +391,7 @@ async function refresh(): Promise<void> {
|
|||||||
fetchFiles(),
|
fetchFiles(),
|
||||||
fetchPermissions(),
|
fetchPermissions(),
|
||||||
fetchUsers(),
|
fetchUsers(),
|
||||||
fetchConfig(),
|
fetchConfig()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const snapshot = {
|
const snapshot = {
|
||||||
@@ -418,7 +402,7 @@ async function refresh(): Promise<void> {
|
|||||||
permissions: res[4],
|
permissions: res[4],
|
||||||
users: res[5],
|
users: res[5],
|
||||||
config: res[6][0],
|
config: res[6][0],
|
||||||
ui: res[6][1],
|
ui: res[6][1]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentSnapshot) {
|
if (currentSnapshot) {
|
||||||
@@ -442,40 +426,45 @@ export async function getCurrentSnapshot(): Promise<string> {
|
|||||||
return currentSnapshot;
|
return currentSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSnapshot(hash: string): boolean {
|
export function hasSnapshot(hash): boolean {
|
||||||
return snapshots.has(hash);
|
return snapshots.has(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPresets(snapshotKey: string): Preset[] {
|
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: string): Provisions {
|
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: string): VirtualParameters {
|
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: string): Files {
|
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(
|
export function getConfig(
|
||||||
snapshotKey: string,
|
snapshotKey,
|
||||||
key: string,
|
key,
|
||||||
context: Record<string, unknown>,
|
context?,
|
||||||
now: number,
|
now?
|
||||||
cb?: (Expression) => Expression
|
|
||||||
): string | number | boolean | null {
|
): 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");
|
||||||
@@ -495,52 +484,39 @@ export function getConfig(
|
|||||||
"CONNECTION_REQUEST_ALLOW_BASIC_AUTH",
|
"CONNECTION_REQUEST_ALLOW_BASIC_AUTH",
|
||||||
"cwmp.maxCommitIterations": "MAX_COMMIT_ITERATIONS",
|
"cwmp.maxCommitIterations": "MAX_COMMIT_ITERATIONS",
|
||||||
"cwmp.deviceOnlineThreshold": "DEVICE_ONLINE_THRESHOLD",
|
"cwmp.deviceOnlineThreshold": "DEVICE_ONLINE_THRESHOLD",
|
||||||
"cwmp.udpConnectionRequestPort": "UDP_CONNECTION_REQUEST_PORT",
|
"cwmp.udpConnectionRequestPort": "UDP_CONNECTION_REQUEST_PORT"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(key in snapshot.config)) {
|
if (!(key in snapshot.config)) {
|
||||||
if (key in oldOpts) {
|
if (key in oldOpts)
|
||||||
let id;
|
return config.get(oldOpts[key], context ? context.id : null);
|
||||||
if (context && context["id"]) {
|
else return null;
|
||||||
id = context["id"];
|
|
||||||
} else if (cb) {
|
|
||||||
id = cb(["PARAM", "DeviceID.ID"]);
|
|
||||||
if (Array.isArray(id)) id = null;
|
|
||||||
}
|
|
||||||
return config.get(oldOpts[key], id);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return expression.evaluate(snapshot.config[key], context, now || Date.now());
|
||||||
const v = expression.evaluate(snapshot.config[key], context, now, cb);
|
|
||||||
return Array.isArray(v) ? null : v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfigExpression(
|
export function getConfigExpression(snapshotKey, key): Expression {
|
||||||
snapshotKey: string,
|
|
||||||
key: string
|
|
||||||
): Expression {
|
|
||||||
const snapshot = snapshots.get(snapshotKey);
|
const snapshot = snapshots.get(snapshotKey);
|
||||||
if (!snapshot) throw new Error("Cache snapshot does not exist");
|
if (!snapshot) throw new Error("Cache snapshot does not exist");
|
||||||
|
|
||||||
return snapshot.config[key];
|
return snapshot.config[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsers(snapshotKey: string): Users {
|
export function getUsers(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.users;
|
return snapshot.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPermissions(snapshotKey: string): Permissions {
|
export function getPermissions(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.permissions;
|
return snapshot.permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUiConfig(snapshotKey: string): UiConfig {
|
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");
|
||||||
|
|
||||||
|
|||||||
+46
-77
@@ -21,15 +21,6 @@ import * as fs from "fs";
|
|||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
|
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import { getRequestOrigin } from "./forwarded";
|
|
||||||
import {
|
|
||||||
SessionContext,
|
|
||||||
AcsRequest,
|
|
||||||
CpeRequest,
|
|
||||||
CpeFault,
|
|
||||||
InformRequest,
|
|
||||||
Fault,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
const REOPEN_EVERY = 60000;
|
const REOPEN_EVERY = 60000;
|
||||||
|
|
||||||
@@ -75,7 +66,7 @@ function reopen(): void {
|
|||||||
if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) {
|
if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) {
|
||||||
logStream.end();
|
logStream.end();
|
||||||
logStream = fs.createWriteStream(null, {
|
logStream = fs.createWriteStream(null, {
|
||||||
fd: fs.openSync(LOG_FILE, "a"),
|
fd: fs.openSync(LOG_FILE, "a")
|
||||||
});
|
});
|
||||||
logStat = fs.fstatSync(logStream.fd);
|
logStat = fs.fstatSync(logStream.fd);
|
||||||
}
|
}
|
||||||
@@ -99,7 +90,7 @@ function reopen(): void {
|
|||||||
) {
|
) {
|
||||||
accessLogStream.end();
|
accessLogStream.end();
|
||||||
accessLogStream = fs.createWriteStream(null, {
|
accessLogStream = fs.createWriteStream(null, {
|
||||||
fd: fs.openSync(ACCESS_LOG_FILE, "a"),
|
fd: fs.openSync(ACCESS_LOG_FILE, "a")
|
||||||
});
|
});
|
||||||
accessLogStat = fs.fstatSync(accessLogStream.fd);
|
accessLogStat = fs.fstatSync(accessLogStream.fd);
|
||||||
}
|
}
|
||||||
@@ -113,7 +104,7 @@ function reopen(): void {
|
|||||||
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init(service: string, version: string): void {
|
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}`;
|
||||||
@@ -129,7 +120,7 @@ export function init(service: string, version: string): void {
|
|||||||
|
|
||||||
if (ACCESS_LOG_FILE) {
|
if (ACCESS_LOG_FILE) {
|
||||||
accessLogStream = fs.createWriteStream(null, {
|
accessLogStream = fs.createWriteStream(null, {
|
||||||
fd: fs.openSync(ACCESS_LOG_FILE, "a"),
|
fd: fs.openSync(ACCESS_LOG_FILE, "a")
|
||||||
});
|
});
|
||||||
accessLogStat = fs.fstatSync(accessLogStream.fd);
|
accessLogStat = fs.fstatSync(accessLogStream.fd);
|
||||||
}
|
}
|
||||||
@@ -155,75 +146,61 @@ export function close(): void {
|
|||||||
logStream.end();
|
logStream.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flatten(
|
export function flatten(details): {} {
|
||||||
details: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
if (details.sessionContext) {
|
if (details.sessionContext) {
|
||||||
const sessionContext = details.sessionContext as SessionContext;
|
details.deviceId = details.sessionContext.deviceId;
|
||||||
details.deviceId = sessionContext.deviceId;
|
details.remoteAddress =
|
||||||
details.remoteAddress = getRequestOrigin(
|
details.sessionContext.httpRequest.connection.remoteAddress;
|
||||||
sessionContext.httpRequest
|
|
||||||
).remoteAddress;
|
|
||||||
delete details.sessionContext;
|
delete details.sessionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.exception) {
|
if (details.exception) {
|
||||||
const err = details.exception as Error;
|
details.exceptionName = details.exception.name;
|
||||||
details.exceptionName = err.name;
|
details.exceptionMessage = details.exception.message;
|
||||||
details.exceptionMessage = err.message;
|
details.exceptionStack = details.exception.stack;
|
||||||
details.exceptionStack = err.stack;
|
|
||||||
delete details.exception;
|
delete details.exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.task) {
|
if (details.task) {
|
||||||
details.taskId = details.task["_id"];
|
details.taskId = details.task._id;
|
||||||
delete details.task;
|
delete details.task;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.rpc) {
|
if (details.rpc) {
|
||||||
const rpc = details.rpc as {
|
if (details.rpc.acsRequest) {
|
||||||
id: string;
|
details.acsRequestId = details.rpc.id;
|
||||||
acsRequest?: AcsRequest;
|
details.acsRequestName = details.rpc.acsRequest.name;
|
||||||
cpeRequest?: CpeRequest;
|
if (details.rpc.acsRequest.commandKey)
|
||||||
cpeFault?: CpeFault;
|
details.acsRequestCommandKey = details.rpc.acsRequest.commandKey;
|
||||||
};
|
} else if (details.rpc.cpeRequest) {
|
||||||
if (rpc.acsRequest) {
|
details.cpeRequestId = details.rpc.id;
|
||||||
details.acsRequestId = rpc.id;
|
if (details.rpc.cpeRequest.name === "Inform") {
|
||||||
details.acsRequestName = rpc.acsRequest.name;
|
details.informEvent = details.rpc.cpeRequest.event.join(",");
|
||||||
if (rpc.acsRequest["commandKey"])
|
details.informRetryCount = details.rpc.cpeRequest.retryCount;
|
||||||
details.acsRequestCommandKey = rpc.acsRequest["commandKey"];
|
|
||||||
} else if (rpc.cpeRequest) {
|
|
||||||
details.cpeRequestId = rpc.id;
|
|
||||||
if (rpc.cpeRequest.name === "Inform") {
|
|
||||||
details.informEvent = (rpc.cpeRequest as InformRequest).event.join(",");
|
|
||||||
details.informRetryCount = (rpc.cpeRequest as InformRequest).retryCount;
|
|
||||||
} else {
|
} else {
|
||||||
details.cpeRequestName = rpc.cpeRequest.name;
|
details.cpeRequestName = details.rpc.cpeRequest.name;
|
||||||
if (rpc.cpeRequest["commandKey"])
|
if (details.rpc.cpeRequest.commandKey)
|
||||||
details.cpeRequestCommandKey = rpc.cpeRequest["commandKey"];
|
details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey;
|
||||||
}
|
}
|
||||||
} else if (rpc.cpeFault) {
|
} else if (details.rpc.cpeFault) {
|
||||||
details.acsRequestId = rpc.id;
|
details.acsRequestId = details.rpc.id;
|
||||||
details.cpeFaultCode = rpc.cpeFault.detail.faultCode;
|
details.cpeFaultCode = details.rpc.cpeFault.detail.faultCode;
|
||||||
details.cpeFaultString = rpc.cpeFault.detail.faultString;
|
details.cpeFaultString = details.rpc.cpeFault.detail.faultString;
|
||||||
}
|
}
|
||||||
delete details.rpc;
|
delete details.rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.fault) {
|
if (details.fault) {
|
||||||
const fault = details.fault as Fault;
|
details.faultCode = details.fault.code;
|
||||||
details.faultCode = fault.code;
|
details.faultMessage = details.fault.message;
|
||||||
details.faultMessage = fault.message;
|
|
||||||
delete details.fault;
|
delete details.fault;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For genieacs-ui
|
// For genieacs-ui
|
||||||
if (details.context) {
|
if (details.context) {
|
||||||
details.remoteAddress = getRequestOrigin(
|
details.remoteAddress = details.context.request.ip;
|
||||||
details.context["req"]
|
if (details.context.state.user)
|
||||||
).remoteAddress;
|
details.user = details.context.state.user.username;
|
||||||
if (details.context["state"].user)
|
|
||||||
details.user = details.context["state"].user.username;
|
|
||||||
delete details.context;
|
delete details.context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +210,7 @@ export function flatten(
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatJson(
|
function formatJson(details, systemd): string {
|
||||||
details: Record<string, unknown>,
|
|
||||||
systemd: boolean
|
|
||||||
): string {
|
|
||||||
if (systemd) {
|
if (systemd) {
|
||||||
let severity = "";
|
let severity = "";
|
||||||
if (details.severity === "info") severity = "<6>";
|
if (details.severity === "info") severity = "<6>";
|
||||||
@@ -249,17 +223,14 @@ function formatJson(
|
|||||||
return `${JSON.stringify(flatten(details))}${os.EOL}`;
|
return `${JSON.stringify(flatten(details))}${os.EOL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSimple(
|
function formatSimple(details, systemd): string {
|
||||||
details: Record<string, unknown>,
|
|
||||||
systemd: boolean
|
|
||||||
): string {
|
|
||||||
const skip = {
|
const skip = {
|
||||||
user: true,
|
user: true,
|
||||||
remoteAddress: true,
|
remoteAddress: true,
|
||||||
severity: true,
|
severity: true,
|
||||||
timestamp: true,
|
timestamp: true,
|
||||||
message: true,
|
message: true,
|
||||||
deviceId: !!details.sessionContext,
|
deviceId: !!details.sessionContext
|
||||||
};
|
};
|
||||||
|
|
||||||
flatten(details);
|
flatten(details);
|
||||||
@@ -292,14 +263,12 @@ function formatSimple(
|
|||||||
return `${severity}${remote}${details.message}${meta}${os.EOL}`;
|
return `${severity}${remote}${details.message}${meta}${os.EOL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${
|
return `${details.timestamp} [${details.severity.toUpperCase()}] ${remote}${
|
||||||
details.timestamp
|
|
||||||
} [${(details.severity as string).toUpperCase()}] ${remote}${
|
|
||||||
details.message
|
details.message
|
||||||
}${meta}${os.EOL}`;
|
}${meta}${os.EOL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(details: Record<string, unknown>): void {
|
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);
|
||||||
@@ -309,22 +278,22 @@ function log(details: Record<string, unknown>): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function info(details: Record<string, unknown>): void {
|
export function info(details): void {
|
||||||
details.severity = "info";
|
details.severity = "info";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function warn(details: Record<string, unknown>): void {
|
export function warn(details): void {
|
||||||
details.severity = "warn";
|
details.severity = "warn";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function error(details: Record<string, unknown>): void {
|
export function error(details): void {
|
||||||
details.severity = "error";
|
details.severity = "error";
|
||||||
log(details);
|
log(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessLog(details: Record<string, unknown>): void {
|
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);
|
||||||
@@ -334,17 +303,17 @@ export function accessLog(details: Record<string, unknown>): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessInfo(details: Record<string, unknown>): void {
|
export function accessInfo(details): void {
|
||||||
details.severity = "info";
|
details.severity = "info";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessWarn(details: Record<string, unknown>): void {
|
export function accessWarn(details): void {
|
||||||
details.severity = "warn";
|
details.severity = "warn";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accessError(details: Record<string, unknown>): void {
|
export function accessError(details): void {
|
||||||
details.severity = "error";
|
details.severity = "error";
|
||||||
accessLog(details);
|
accessLog(details);
|
||||||
}
|
}
|
||||||
+215
-242
@@ -18,14 +18,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ObjectID } from "mongodb";
|
import { ObjectID } from "mongodb";
|
||||||
import { map, parse, stringify, parseList } from "./common/expression-parser";
|
import { map, parse, stringify } from "./common/expression-parser";
|
||||||
import { likePatternToRegExp } from "./common/expression";
|
import { likePatternToRegExp, evaluate } from "./common/expression";
|
||||||
import { Expression, Fault, Task } from "./types";
|
import { Expression, Fault, Task } from "./types";
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
export function processDeviceFilter(filter: Expression): Expression {
|
export function processDeviceFilter(filter): Expression {
|
||||||
return map(filter, (exp) => {
|
return map(filter, exp => {
|
||||||
if (!isArray(exp)) return exp;
|
if (!isArray(exp)) return exp;
|
||||||
|
|
||||||
if (exp[0] === "PARAM") {
|
if (exp[0] === "PARAM") {
|
||||||
@@ -44,25 +44,16 @@ export function processDeviceFilter(filter: Expression): Expression {
|
|||||||
exp[1][1].startsWith("Tags.")
|
exp[1][1].startsWith("Tags.")
|
||||||
) {
|
) {
|
||||||
const t = exp[1][1].slice(5);
|
const t = exp[1][1].slice(5);
|
||||||
if (exp[0] === "IS NULL") return ["_tags", t, false];
|
if (exp[0] === "IS NULL") return ["<>", ["PARAM", "_tags"], t];
|
||||||
else if (exp[0] === "IS NOT NULL") return ["_tags", t, true];
|
else if (exp[0] === "IS NOT NULL") return ["=", ["PARAM", "_tags"], t];
|
||||||
else if (exp[0] === "=" && exp[2] === true) return ["_tags", t, true];
|
else if (exp[0] === "=" && exp[2] === true)
|
||||||
else if (exp[0] === "<>" && exp[2] !== true) return ["_tags", t, true];
|
return ["=", ["PARAM", "_tags"], t];
|
||||||
|
else if (exp[0] === "<>" && exp[2] !== true)
|
||||||
|
return ["=", ["PARAM", "_tags"], t];
|
||||||
} else if (
|
} else if (
|
||||||
[
|
["=", "<>", ">", ">=", "<", "<=", "LIKE", "NOT LIKE"].includes(exp[0])
|
||||||
"=",
|
|
||||||
"<>",
|
|
||||||
">",
|
|
||||||
">=",
|
|
||||||
"<",
|
|
||||||
"<=",
|
|
||||||
"LIKE",
|
|
||||||
"NOT LIKE",
|
|
||||||
"IS NULL",
|
|
||||||
"IS NOT NULL",
|
|
||||||
].includes(exp[0])
|
|
||||||
) {
|
) {
|
||||||
let e = map(exp, (ee) => {
|
let e = map(exp, ee => {
|
||||||
if (
|
if (
|
||||||
isArray(ee) &&
|
isArray(ee) &&
|
||||||
ee[0] === "PARAM" &&
|
ee[0] === "PARAM" &&
|
||||||
@@ -84,8 +75,8 @@ export function processDeviceFilter(filter: Expression): Expression {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processTasksFilter(filter: Expression): Expression {
|
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])) {
|
||||||
const e = exp.slice();
|
const e = exp.slice();
|
||||||
@@ -100,8 +91,8 @@ export function processTasksFilter(filter: Expression): Expression {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFaultsFilter(filter: Expression): Expression {
|
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])) {
|
||||||
const e = exp.slice();
|
const e = exp.slice();
|
||||||
@@ -114,137 +105,140 @@ export function processFaultsFilter(filter: Expression): Expression {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterToMongoQuery(exp: Expression): Record<string, unknown> {
|
export function filterToMongoQuery(exp: Expression): {} {
|
||||||
function recursive(filter, negate): Record<string, unknown> {
|
const ops = {
|
||||||
|
OR: 0,
|
||||||
|
AND: 0,
|
||||||
|
NOT: 0,
|
||||||
|
"=": 1,
|
||||||
|
"<>": 1,
|
||||||
|
">": 1,
|
||||||
|
">=": 1,
|
||||||
|
"<": 1,
|
||||||
|
"<=": 1,
|
||||||
|
LIKE: 1,
|
||||||
|
"NOT LIKE": 1,
|
||||||
|
"IS NULL": 1,
|
||||||
|
"IS NOT NULL": 1
|
||||||
|
};
|
||||||
|
|
||||||
|
function recursive(filter, negate, res = {}): {} {
|
||||||
const op = filter[0];
|
const op = filter[0];
|
||||||
|
|
||||||
if (op === "AND") {
|
if (ops[op] === 0) {
|
||||||
filter = filter.filter((f) => f !== true);
|
for (let i = 1; i < filter.length; ++i) {
|
||||||
if (filter.length === 2) return recursive(filter[1], negate);
|
if (!isArray(filter[i]) || ops[filter[i][0]] == null)
|
||||||
}
|
throw new Error(`Invalid expression in ${op} clause`);
|
||||||
|
|
||||||
if (op === "OR") {
|
|
||||||
filter = filter.filter((f) => f !== false);
|
|
||||||
if (filter.length === 2) return recursive(filter[1], negate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!negate && op === "AND") || (negate && op === "OR"))
|
|
||||||
return { $and: filter.slice(1).map((f) => recursive(f, negate)) };
|
|
||||||
|
|
||||||
if ((!negate && op === "OR") || (negate && op === "AND"))
|
|
||||||
return { $or: filter.slice(1).map((f) => recursive(f, negate)) };
|
|
||||||
|
|
||||||
if (op === "NOT") return recursive(filter[1], !negate);
|
|
||||||
|
|
||||||
if (isArray(filter[2]))
|
|
||||||
throw new Error(`Invalid RHS operand of ${op} clause`);
|
|
||||||
|
|
||||||
if (op === "LIKE" || op === "NOT LIKE") {
|
|
||||||
if (op === "NOT LIKE") negate = !negate;
|
|
||||||
let param;
|
|
||||||
let flags;
|
|
||||||
if (!Array.isArray(filter[1]))
|
|
||||||
throw new Error(`Invalid LHS operand of ${op} clause`);
|
|
||||||
if (filter[1][0] === "FUNC" && filter[1][1] === "UPPER") {
|
|
||||||
if (filter[2] !== filter[2].toUpperCase())
|
|
||||||
throw new Error(`Invalid RHS operand of ${op} clause`);
|
|
||||||
param = filter[1][2][1];
|
|
||||||
flags = "i";
|
|
||||||
} else if (filter[1][0] === "FUNC" && filter[1][1] === "LOWER") {
|
|
||||||
if (filter[2] !== filter[2].toLowerCase())
|
|
||||||
throw new Error(`Invalid RHS operand of ${op} clause`);
|
|
||||||
param = filter[1][2][1];
|
|
||||||
flags = "i";
|
|
||||||
} else if (filter[1][0] === "PARAM") {
|
|
||||||
param = filter[1][1];
|
|
||||||
flags = "";
|
|
||||||
} else {
|
|
||||||
throw new Error(`Invalid LHS operand of ${op} clause`);
|
|
||||||
}
|
}
|
||||||
const r = likePatternToRegExp(filter[2], filter[3], flags);
|
|
||||||
if (negate) return { [param]: { $nin: [r, null] } };
|
if ((!negate && op === "AND") || (negate && op === "OR")) {
|
||||||
else return { [param]: r };
|
res["$and"] = res["$and"] || [];
|
||||||
|
for (let i = 1; i < filter.length; ++i)
|
||||||
|
res["$and"].push(recursive(filter[i], negate));
|
||||||
|
} else if ((!negate && op === "OR") || (negate && op === "AND")) {
|
||||||
|
res["$or"] = res["$or"] || [];
|
||||||
|
|
||||||
|
for (let i = 1; i < filter.length; ++i)
|
||||||
|
res["$or"].push(recursive(filter[i], negate));
|
||||||
|
} else if (op === "NOT") {
|
||||||
|
recursive(filter[1], !negate, res);
|
||||||
|
}
|
||||||
|
} else if (ops[op] === 1) {
|
||||||
|
if (isArray(filter[2])) {
|
||||||
|
throw new Error(`Right operand of ${op} clause is not a primitive`);
|
||||||
|
} else if (
|
||||||
|
["LIKE", "NOT LIKE"].includes(op) &&
|
||||||
|
isArray(filter[1]) &&
|
||||||
|
filter[1][0] === "FUNC" &&
|
||||||
|
["UPPER", "LOWER"].includes(filter[1][1])
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
(filter[1][1] === "UPPER" && filter[2] !== filter[2].toUpperCase()) ||
|
||||||
|
(filter[1][1] === "LOWER" && filter[2] !== filter[2].toLowerCase())
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot compare ${
|
||||||
|
filter[1][1]
|
||||||
|
}() against non-${filter[1][1].toLowerCase()}case pattern`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (!isArray(filter[1]) || filter[1][0] !== "PARAM") {
|
||||||
|
throw new Error(`Left operand of ${op} clause is not a parameter`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op === "=") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
const p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p["$ne"] = filter[2];
|
||||||
|
else p["$eq"] = filter[2];
|
||||||
|
} else if (op === "<>") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
let p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p = p["$not"] = p["$not"] || {};
|
||||||
|
p["$ne"] = filter[2];
|
||||||
|
if (param !== "_tags") p["$exists"] = true;
|
||||||
|
} else if (op === ">") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
let p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p = p["$not"] = p["$not"] || {};
|
||||||
|
p["$gt"] = filter[2];
|
||||||
|
} else if (op === ">=") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
let p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p = p["$not"] = p["$not"] || {};
|
||||||
|
p["$gte"] = filter[2];
|
||||||
|
} else if (op === "<") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
let p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p = p["$not"] = p["$not"] || {};
|
||||||
|
p["$lt"] = filter[2];
|
||||||
|
} else if (op === "<=") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
let p = (res[param] = res[param] || {});
|
||||||
|
if (negate) p = p["$not"] = p["$not"] || {};
|
||||||
|
p["$lte"] = filter[2];
|
||||||
|
} else if (op === "IS NULL") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
res[param] = { $exists: negate };
|
||||||
|
} else if (op === "IS NOT NULL") {
|
||||||
|
const param = filter[1][1];
|
||||||
|
res[param] = { $exists: !negate };
|
||||||
|
} else {
|
||||||
|
if (op === "NOT LIKE") negate = !negate;
|
||||||
|
let param;
|
||||||
|
let flags;
|
||||||
|
if (filter[1][0] === "FUNC" && filter[1][1] === "UPPER") {
|
||||||
|
param = filter[1][2][1];
|
||||||
|
flags = "i";
|
||||||
|
} else if (filter[1][0] === "FUNC" && filter[1][1] === "LOWER") {
|
||||||
|
param = filter[1][2][1];
|
||||||
|
flags = "i";
|
||||||
|
} else {
|
||||||
|
param = filter[1][1];
|
||||||
|
flags = "";
|
||||||
|
}
|
||||||
|
const r = likePatternToRegExp(filter[2], filter[3], flags);
|
||||||
|
if (negate) res[param] = { $not: r };
|
||||||
|
else res[param] = r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unrecognized operator ${op}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op === "_tags") {
|
return res;
|
||||||
let t = filter[2];
|
|
||||||
if (negate) t = !t;
|
|
||||||
if (t) return { _tags: filter[1] };
|
|
||||||
else return { _tags: { $ne: filter[1] } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isArray(filter[1]) || filter[1][0] !== "PARAM")
|
|
||||||
throw new Error(`Invalid LHS operand of ${op} clause`);
|
|
||||||
|
|
||||||
if (op === "IS NULL") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
return { [p]: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === "IS NOT NULL") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
return { [p]: { $ne: null } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isArray(filter[2]) || filter[2] == null)
|
|
||||||
throw new Error(`Invalid RHS operand of ${op} clause`);
|
|
||||||
|
|
||||||
if (op === "=") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (!negate) return { [p]: v };
|
|
||||||
else return { [p]: { $nin: [v, null] } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === "<>") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (negate) return { [p]: v };
|
|
||||||
else return { [p]: { $nin: [v, null] } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === ">") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (negate) return { [p]: { $lte: v } };
|
|
||||||
else return { [p]: { $gt: v } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === ">=") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (negate) return { [p]: { $lt: v } };
|
|
||||||
else return { [p]: { $gte: v } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === "<") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (negate) return { [p]: { $gte: v } };
|
|
||||||
else return { [p]: { $lt: v } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === "<=") {
|
|
||||||
const p = filter[1][1];
|
|
||||||
const v = filter[2];
|
|
||||||
if (negate) return { [p]: { $gt: v } };
|
|
||||||
else return { [p]: { $lte: v } };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unrecognized operator ${op}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isArray(exp)) {
|
const _exp = evaluate(exp, null, Date.now());
|
||||||
if (exp === true) return {};
|
|
||||||
|
if (!isArray(_exp)) {
|
||||||
|
if (_exp === true) return {};
|
||||||
throw new Error("Primitives are not valid queries");
|
throw new Error("Primitives are not valid queries");
|
||||||
}
|
}
|
||||||
|
|
||||||
return recursive(exp, false);
|
return recursive(_exp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processDeviceProjection(
|
export function processDeviceProjection(projection: {}): {} {
|
||||||
projection: Record<string, 1>
|
|
||||||
): Record<string, 1> {
|
|
||||||
if (!projection) return projection;
|
if (!projection) return projection;
|
||||||
const p = {};
|
const p = {};
|
||||||
for (const [k, v] of Object.entries(projection)) {
|
for (const [k, v] of Object.entries(projection)) {
|
||||||
@@ -270,9 +264,7 @@ export function processDeviceProjection(
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processDeviceSort(
|
export function processDeviceSort(sort: {}): {} {
|
||||||
sort: Record<string, number>
|
|
||||||
): Record<string, number> {
|
|
||||||
if (!sort) return sort;
|
if (!sort) return sort;
|
||||||
const s = {};
|
const s = {};
|
||||||
for (const [k, v] of Object.entries(sort)) {
|
for (const [k, v] of Object.entries(sort)) {
|
||||||
@@ -288,7 +280,7 @@ export function processDeviceSort(
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDate(d: Date): number | string {
|
function parseDate(d): number | string {
|
||||||
const n = +d;
|
const n = +d;
|
||||||
return isNaN(n) ? "" + d : n;
|
return isNaN(n) ? "" + d : n;
|
||||||
}
|
}
|
||||||
@@ -310,7 +302,7 @@ interface FlatDevice {
|
|||||||
[param: string]: FlatAttributes;
|
[param: string]: FlatAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
export function flattenDevice(device): FlatDevice {
|
||||||
function recursive(
|
function recursive(
|
||||||
input,
|
input,
|
||||||
root: string,
|
root: string,
|
||||||
@@ -321,39 +313,39 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
if (!root) {
|
if (!root) {
|
||||||
if (name === "_lastInform") {
|
if (name === "_lastInform") {
|
||||||
output["Events.Inform"] = {
|
output["Events.Inform"] = {
|
||||||
value: [parseDate(tree as Date), "xsd:dateTime"],
|
value: [parseDate(tree), "xsd:dateTime"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_registered") {
|
} else if (name === "_registered") {
|
||||||
output["Events.Registered"] = {
|
output["Events.Registered"] = {
|
||||||
value: [parseDate(tree as Date), "xsd:dateTime"],
|
value: [parseDate(tree), "xsd:dateTime"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_lastBoot") {
|
} else if (name === "_lastBoot") {
|
||||||
output["Events.1_BOOT"] = {
|
output["Events.1_BOOT"] = {
|
||||||
value: [parseDate(tree as Date), "xsd:dateTime"],
|
value: [parseDate(tree), "xsd:dateTime"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_lastBootstrap") {
|
} else if (name === "_lastBootstrap") {
|
||||||
output["Events.0_BOOTSTRAP"] = {
|
output["Events.0_BOOTSTRAP"] = {
|
||||||
value: [parseDate(tree as Date), "xsd:dateTime"],
|
value: [parseDate(tree), "xsd:dateTime"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_id") {
|
} else if (name === "_id") {
|
||||||
output["DeviceID.ID"] = {
|
output["DeviceID.ID"] = {
|
||||||
@@ -362,7 +354,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_deviceId") {
|
} else if (name === "_deviceId") {
|
||||||
output["DeviceID.Manufacturer"] = {
|
output["DeviceID.Manufacturer"] = {
|
||||||
@@ -371,7 +363,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
output["DeviceID.OUI"] = {
|
output["DeviceID.OUI"] = {
|
||||||
value: [tree["_OUI"], "xsd:string"],
|
value: [tree["_OUI"], "xsd:string"],
|
||||||
@@ -379,7 +371,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
output["DeviceID.ProductClass"] = {
|
output["DeviceID.ProductClass"] = {
|
||||||
value: [tree["_ProductClass"], "xsd:string"],
|
value: [tree["_ProductClass"], "xsd:string"],
|
||||||
@@ -387,7 +379,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
output["DeviceID.SerialNumber"] = {
|
output["DeviceID.SerialNumber"] = {
|
||||||
value: [tree["_SerialNumber"], "xsd:string"],
|
value: [tree["_SerialNumber"], "xsd:string"],
|
||||||
@@ -395,14 +387,14 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
} else if (name === "_tags") {
|
} else if (name === "_tags") {
|
||||||
output["Tags"] = {
|
output["Tags"] = {
|
||||||
writable: false,
|
writable: false,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: true,
|
object: true,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const t of tree as string[]) {
|
for (const t of tree as string[]) {
|
||||||
@@ -412,7 +404,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
writable: true,
|
writable: true,
|
||||||
writableTimestamp: timestamp,
|
writableTimestamp: timestamp,
|
||||||
object: false,
|
object: false,
|
||||||
objectTimestamp: timestamp,
|
objectTimestamp: timestamp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,7 +422,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
if (tree["_value"] != null) {
|
if (tree["_value"] != null) {
|
||||||
attrs.value = [
|
attrs.value = [
|
||||||
tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"],
|
tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"],
|
||||||
tree["_type"],
|
tree["_type"]
|
||||||
];
|
];
|
||||||
attrs.valueTimestamp = +(tree["_timestamp"] || childrenTimestamp);
|
attrs.valueTimestamp = +(tree["_timestamp"] || childrenTimestamp);
|
||||||
attrs.object = false;
|
attrs.object = false;
|
||||||
@@ -464,29 +456,27 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newDevice: FlatDevice = {};
|
const newDevice: FlatDevice = {};
|
||||||
const timestamp = new Date((device["_lastInform"] as Date) || 1).getTime();
|
const timestamp = new Date(device["_lastInform"] || 1).getTime();
|
||||||
recursive(device, "", newDevice, timestamp);
|
recursive(device, "", newDevice, timestamp);
|
||||||
return newDevice;
|
return newDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenFault(fault: unknown): Fault {
|
export function flattenFault(fault): Fault {
|
||||||
const f = Object.assign({}, fault) as 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 as Fault;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenTask(task: unknown): Task {
|
export function flattenTask(task): Task {
|
||||||
const t = Object.assign({}, task) as 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;
|
||||||
if (t.expiry) t.expiry = +t.expiry;
|
if (t.expiry) t.expiry = +t.expiry;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertOldPrecondition(
|
export function mongoQueryToFilter(query): Expression {
|
||||||
query: Record<string, unknown>
|
|
||||||
): Expression {
|
|
||||||
function recursive(_query): Expression {
|
function recursive(_query): Expression {
|
||||||
const expressions: Expression[] = [];
|
const expressions: Expression[] = [];
|
||||||
for (const [k, v] of Object.entries(_query)) {
|
for (const [k, v] of Object.entries(_query)) {
|
||||||
@@ -505,22 +495,12 @@ export function convertOldPrecondition(
|
|||||||
} else if (k === "_tags") {
|
} else if (k === "_tags") {
|
||||||
if (typeof v === "object") {
|
if (typeof v === "object") {
|
||||||
if (isArray(v)) throw new Error(`Invalid type`);
|
if (isArray(v)) throw new Error(`Invalid type`);
|
||||||
const conjs: Expression[] = [];
|
|
||||||
for (const [op, val] of Object.entries(v)) {
|
if (v.hasOwnProperty("$ne"))
|
||||||
if (op === "$ne") {
|
expressions.push(["IS NULL", ["PARAM", `Tags.${v["$ne"]}`]]);
|
||||||
if (typeof v["$ne"] !== "string")
|
else if (v.hasOwnProperty("$eq"))
|
||||||
throw new Error("Only string values are allowed for _tags");
|
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v["$eq"]}`]]);
|
||||||
conjs.push(["IS NULL", ["PARAM", `Tags.${val}`]]);
|
else throw new Error(`Invalid tag query`);
|
||||||
} else if (op === "$eq") {
|
|
||||||
if (typeof v["$eq"] !== "string")
|
|
||||||
throw new Error("Only string values are allowed for _tags");
|
|
||||||
conjs.push(["IS NOT NULL", ["PARAM", `Tags.${val}`]]);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Invalid tag query`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (conjs.length === 1) expressions.push(conjs[0]);
|
|
||||||
else if (conjs.length > 1) expressions.push(["AND", ...conjs]);
|
|
||||||
} else {
|
} else {
|
||||||
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]);
|
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]);
|
||||||
}
|
}
|
||||||
@@ -538,31 +518,34 @@ export function convertOldPrecondition(
|
|||||||
|
|
||||||
const exps: Expression[] = [];
|
const exps: Expression[] = [];
|
||||||
for (const [kk, vv] of Object.entries(v)) {
|
for (const [kk, vv] of Object.entries(v)) {
|
||||||
if (kk === "$eq") {
|
let op;
|
||||||
exps.push(["=", ["PARAM", k], vv]);
|
switch (kk) {
|
||||||
} else if (kk === "$ne") {
|
case "$eq":
|
||||||
exps.push([
|
op = "=";
|
||||||
"OR",
|
break;
|
||||||
["<>", ["PARAM", k], vv],
|
case "$ne":
|
||||||
["IS NULL", ["PARAM", k]],
|
op = "<>";
|
||||||
]);
|
break;
|
||||||
} else if (kk === "$lt") {
|
case "$lt":
|
||||||
exps.push(["<", ["PARAM", k], vv]);
|
op = "<";
|
||||||
} else if (kk === "$lte") {
|
break;
|
||||||
exps.push(["<=", ["PARAM", k], vv]);
|
case "$lte":
|
||||||
} else if (kk === "$gt") {
|
op = "<=";
|
||||||
exps.push([">", ["PARAM", k], vv]);
|
break;
|
||||||
} else if (kk === "$gte") {
|
case "$gt":
|
||||||
exps.push([">=", ["PARAM", k], vv]);
|
op = ">";
|
||||||
} else {
|
break;
|
||||||
throw new Error(`Operator ${kk} not supported`);
|
case "$gte":
|
||||||
|
op = ">=";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Operator ${kk} not supported`);
|
||||||
}
|
}
|
||||||
if (!["string", "number", "boolean"].includes(typeof vv))
|
exps.push([op, ["PARAM", k], vv]);
|
||||||
throw new Error(`Invalid value for ${kk} operator`);
|
|
||||||
}
|
}
|
||||||
if (exps.length === 1) {
|
if (exps.length === 1) {
|
||||||
expressions.push(exps[0]);
|
expressions.push(exps[0]);
|
||||||
} else if (exps.length > 1) {
|
} else {
|
||||||
const and: Expression = ["AND"];
|
const and: Expression = ["AND"];
|
||||||
expressions.push(and.concat(exps));
|
expressions.push(and.concat(exps));
|
||||||
}
|
}
|
||||||
@@ -571,8 +554,8 @@ export function convertOldPrecondition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (expressions.length === 1) return expressions[0];
|
if (expressions.length === 1) return expressions[0];
|
||||||
if (expressions.length === 0) return true;
|
const and: Expression = ["AND"];
|
||||||
return ["AND", ...expressions];
|
return and.concat(expressions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty filter
|
// empty filter
|
||||||
@@ -581,21 +564,15 @@ export function convertOldPrecondition(
|
|||||||
return recursive(query);
|
return recursive(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenPreset(
|
export function flattenPreset(preset): {} {
|
||||||
preset: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const p = Object.assign({}, preset);
|
const p = Object.assign({}, preset);
|
||||||
if (p.precondition) {
|
if (p.precondition) {
|
||||||
try {
|
try {
|
||||||
// Try parse to check expression validity
|
// Try parse to check expression validity
|
||||||
parse(p.precondition as string);
|
parse(p.precondition);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
p.precondition = convertOldPrecondition(
|
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
|
||||||
JSON.parse(p.precondition as string)
|
p.precondition = p.precondition.length ? stringify(p.precondition) : "";
|
||||||
);
|
|
||||||
p.precondition = (p.precondition as string).length
|
|
||||||
? stringify(p.precondition as Expression)
|
|
||||||
: "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,14 +584,14 @@ export function flattenPreset(
|
|||||||
|
|
||||||
const provision = p.configurations[0];
|
const provision = p.configurations[0];
|
||||||
if (
|
if (
|
||||||
(p.configurations as any[]).length === 1 &&
|
p.configurations.length === 1 &&
|
||||||
provision.type === "provision" &&
|
provision.type === "provision" &&
|
||||||
provision.name &&
|
provision.name &&
|
||||||
provision.name.length
|
provision.name.length
|
||||||
) {
|
) {
|
||||||
p.provision = provision.name;
|
p.provision = provision.name;
|
||||||
p.provisionArgs = provision.args
|
p.provisionArgs = provision.args
|
||||||
? provision.args.map((a) => stringify(a)).join(", ")
|
? JSON.stringify(provision.args).slice(1, -1)
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,34 +599,30 @@ export function flattenPreset(
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenFile(
|
export function flattenFile(file): {} {
|
||||||
file: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const f = {};
|
const f = {};
|
||||||
f["_id"] = file["_id"];
|
f["_id"] = file["_id"];
|
||||||
if (file.metadata) {
|
if (file.metadata) {
|
||||||
f["metadata.fileType"] = file["metadata"]["fileType"] || "";
|
f["metadata.fileType"] = file.metadata.fileType || "";
|
||||||
f["metadata.oui"] = file["metadata"]["oui"] || "";
|
f["metadata.oui"] = file.metadata.oui || "";
|
||||||
f["metadata.productClass"] = file["metadata"]["productClass"] || "";
|
f["metadata.productClass"] = file.metadata.productClass || "";
|
||||||
f["metadata.version"] = file["metadata"]["version"] || "";
|
f["metadata.version"] = file.metadata.version || "";
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function preProcessPreset(
|
export function preProcessPreset(data): {} {
|
||||||
data: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const preset = Object.assign({}, data);
|
const preset = Object.assign({}, data);
|
||||||
|
|
||||||
if (!preset.precondition) preset.precondition = "";
|
if (!preset.precondition) preset.precondition = "";
|
||||||
// Try parse to check expression validity
|
// Try parse to check expression validity
|
||||||
parse(preset.precondition as string);
|
parse(preset.precondition);
|
||||||
|
|
||||||
preset.weight = parseInt(preset.weight as string) || 0;
|
preset.weight = parseInt(preset.weight) || 0;
|
||||||
|
|
||||||
const events = {};
|
const events = {};
|
||||||
if (preset.events) {
|
if (preset.events) {
|
||||||
for (let e of (preset.events as string).split(",")) {
|
for (let e of preset.events.split(",")) {
|
||||||
let v = true;
|
let v = true;
|
||||||
e = e.trim();
|
e = e.trim();
|
||||||
if (e.startsWith("-")) {
|
if (e.startsWith("-")) {
|
||||||
@@ -667,11 +640,11 @@ export function preProcessPreset(
|
|||||||
const configuration = {
|
const configuration = {
|
||||||
type: "provision",
|
type: "provision",
|
||||||
name: preset.provision,
|
name: preset.provision,
|
||||||
args: null,
|
args: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preset.provisionArgs)
|
if (preset.provisionArgs)
|
||||||
configuration.args = parseList(preset.provisionArgs as string);
|
configuration.args = JSON.parse(`[${preset.provisionArgs}]`);
|
||||||
|
|
||||||
delete preset.provision;
|
delete preset.provision;
|
||||||
delete preset.provisionArgs;
|
delete preset.provisionArgs;
|
||||||
|
|||||||
+66
-90
@@ -25,7 +25,6 @@ import * as config from "./config";
|
|||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as query from "./query";
|
import * as query from "./query";
|
||||||
import * as apiFunctions from "./api-functions";
|
import * as apiFunctions from "./api-functions";
|
||||||
import { IncomingMessage, ServerResponse } from "http";
|
|
||||||
import * as cache from "./cache";
|
import * as cache from "./cache";
|
||||||
import { version as VERSION } from "../package.json";
|
import { version as VERSION } from "../package.json";
|
||||||
import { ping } from "./ping";
|
import { ping } from "./ping";
|
||||||
@@ -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: Error, httpResponse?: ServerResponse): never {
|
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,10 +50,7 @@ function throwError(err: Error, httpResponse?: ServerResponse): never {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listener(
|
export function listener(request, response): void {
|
||||||
request: IncomingMessage,
|
|
||||||
response: ServerResponse
|
|
||||||
): void {
|
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
let bytes = 0;
|
let bytes = 0;
|
||||||
response.setHeader("GenieACS-Version", VERSION);
|
response.setHeader("GenieACS-Version", VERSION);
|
||||||
@@ -89,7 +85,7 @@ export function listener(
|
|||||||
{ _id: presetName },
|
{ _id: presetName },
|
||||||
preset,
|
preset,
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -98,7 +94,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -106,7 +102,7 @@ export function listener(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (request.method === "DELETE") {
|
} else if (request.method === "DELETE") {
|
||||||
db.presetsCollection.deleteOne({ _id: presetName }, (err) => {
|
db.presetsCollection.deleteOne({ _id: presetName }, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -115,7 +111,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -136,7 +132,7 @@ export function listener(
|
|||||||
{ _id: objectName },
|
{ _id: objectName },
|
||||||
object,
|
object,
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -145,7 +141,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -153,7 +149,7 @@ export function listener(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (request.method === "DELETE") {
|
} else if (request.method === "DELETE") {
|
||||||
db.objectsCollection.deleteOne({ _id: objectName }, (err) => {
|
db.objectsCollection.deleteOne({ _id: objectName }, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -162,7 +158,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -179,7 +175,7 @@ export function listener(
|
|||||||
if (request.method === "PUT") {
|
if (request.method === "PUT") {
|
||||||
const object = {
|
const object = {
|
||||||
_id: provisionName,
|
_id: provisionName,
|
||||||
script: body.toString(),
|
script: body.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -194,7 +190,7 @@ export function listener(
|
|||||||
{ _id: provisionName },
|
{ _id: provisionName },
|
||||||
object,
|
object,
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -203,7 +199,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -211,7 +207,7 @@ export function listener(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (request.method === "DELETE") {
|
} else if (request.method === "DELETE") {
|
||||||
db.provisionsCollection.deleteOne({ _id: provisionName }, (err) => {
|
db.provisionsCollection.deleteOne({ _id: provisionName }, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -220,7 +216,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -237,7 +233,7 @@ export function listener(
|
|||||||
if (request.method === "PUT") {
|
if (request.method === "PUT") {
|
||||||
const object = {
|
const object = {
|
||||||
_id: virtualParameterName,
|
_id: virtualParameterName,
|
||||||
script: body.toString(),
|
script: body.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -252,7 +248,7 @@ export function listener(
|
|||||||
{ _id: virtualParameterName },
|
{ _id: virtualParameterName },
|
||||||
object,
|
object,
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -261,7 +257,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -271,7 +267,7 @@ export function listener(
|
|||||||
} else if (request.method === "DELETE") {
|
} else if (request.method === "DELETE") {
|
||||||
db.virtualParametersCollection.deleteOne(
|
db.virtualParametersCollection.deleteOne(
|
||||||
{ _id: virtualParameterName },
|
{ _id: virtualParameterName },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -280,7 +276,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -299,7 +295,7 @@ export function listener(
|
|||||||
db.devicesCollection.updateOne(
|
db.devicesCollection.updateOne(
|
||||||
{ _id: deviceId },
|
{ _id: deviceId },
|
||||||
{ $addToSet: { _tags: tag } },
|
{ $addToSet: { _tags: tag } },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
@@ -309,7 +305,7 @@ export function listener(
|
|||||||
db.devicesCollection.updateOne(
|
db.devicesCollection.updateOne(
|
||||||
{ _id: deviceId },
|
{ _id: deviceId },
|
||||||
{ $pull: { _tags: tag } },
|
{ $pull: { _tags: tag } },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
@@ -327,12 +323,12 @@ export function listener(
|
|||||||
);
|
);
|
||||||
const deviceId = faultId.split(":", 1)[0];
|
const deviceId = faultId.split(":", 1)[0];
|
||||||
const channel = faultId.slice(deviceId.length + 1);
|
const channel = faultId.slice(deviceId.length + 1);
|
||||||
db.faultsCollection.deleteOne({ _id: faultId }, (err) => {
|
db.faultsCollection.deleteOne({ _id: faultId }, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
if (channel.startsWith("task_")) {
|
if (channel.startsWith("task_")) {
|
||||||
const objId = new mongodb.ObjectID(channel.slice(5));
|
const objId = new mongodb.ObjectID(channel.slice(5));
|
||||||
return void db.tasksCollection.deleteOne({ _id: objId }, (err) => {
|
return void db.tasksCollection.deleteOne({ _id: objId }, err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -341,7 +337,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -355,7 +351,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -386,20 +382,17 @@ export function listener(
|
|||||||
const taskTimeout =
|
const taskTimeout =
|
||||||
(urlParts.query.timeout &&
|
(urlParts.query.timeout &&
|
||||||
parseInt(urlParts.query.timeout as string)) ||
|
parseInt(urlParts.query.timeout as string)) ||
|
||||||
(config.get(
|
config.get("DEVICE_ONLINE_THRESHOLD", deviceId);
|
||||||
"DEVICE_ONLINE_THRESHOLD",
|
|
||||||
deviceId
|
|
||||||
) as number);
|
|
||||||
|
|
||||||
apiFunctions
|
apiFunctions
|
||||||
.watchTask(deviceId, task._id, taskTimeout)
|
.watchTask(deviceId, task._id, taskTimeout)
|
||||||
.then((status) => {
|
.then(status => {
|
||||||
if (status === "timeout") {
|
if (status === "timeout") {
|
||||||
response.writeHead(
|
response.writeHead(
|
||||||
202,
|
202,
|
||||||
"Task queued but not processed",
|
"Task queued but not processed",
|
||||||
{
|
{
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
response.end(JSON.stringify(task));
|
response.end(JSON.stringify(task));
|
||||||
@@ -411,44 +404,44 @@ export function listener(
|
|||||||
return void throwError(err, response);
|
return void throwError(err, response);
|
||||||
|
|
||||||
response.writeHead(202, "Task faulted", {
|
response.writeHead(202, "Task faulted", {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
});
|
});
|
||||||
response.end(JSON.stringify(task2));
|
response.end(JSON.stringify(task2));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
});
|
});
|
||||||
response.end(JSON.stringify(task));
|
response.end(JSON.stringify(task));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
response.writeHead(202, err.message, {
|
response.writeHead(202, err.message, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
});
|
});
|
||||||
response.end(JSON.stringify(task));
|
response.end(JSON.stringify(task));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(202, {
|
response.writeHead(202, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
});
|
});
|
||||||
response.end(JSON.stringify(task));
|
response.end(JSON.stringify(task));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -461,7 +454,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
response.writeHead(504);
|
response.writeHead(504);
|
||||||
response.end(`${err.name}: ${err.message}`);
|
response.end(`${err.name}: ${err.message}`);
|
||||||
});
|
});
|
||||||
@@ -494,12 +487,12 @@ export function listener(
|
|||||||
const deviceId = task.device;
|
const deviceId = task.device;
|
||||||
db.tasksCollection.deleteOne(
|
db.tasksCollection.deleteOne(
|
||||||
{ _id: new mongodb.ObjectID(taskId) },
|
{ _id: new mongodb.ObjectID(taskId) },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
db.faultsCollection.deleteOne(
|
db.faultsCollection.deleteOne(
|
||||||
{ _id: `${deviceId}:task_${taskId}` },
|
{ _id: `${deviceId}:task_${taskId}` },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -508,7 +501,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -534,7 +527,7 @@ export function listener(
|
|||||||
const deviceId = task.device;
|
const deviceId = task.device;
|
||||||
db.faultsCollection.deleteOne(
|
db.faultsCollection.deleteOne(
|
||||||
{ _id: `${deviceId}:task_${taskId}` },
|
{ _id: `${deviceId}:task_${taskId}` },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err, response);
|
if (err) return void throwError(err, response);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
@@ -543,7 +536,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -569,38 +562,25 @@ export function listener(
|
|||||||
fileType: request.headers.filetype,
|
fileType: request.headers.filetype,
|
||||||
oui: request.headers.oui,
|
oui: request.headers.oui,
|
||||||
productClass: request.headers.productclass,
|
productClass: request.headers.productclass,
|
||||||
version: request.headers.version,
|
version: request.headers.version
|
||||||
};
|
};
|
||||||
const bucket = new mongodb.GridFSBucket(db.client.db());
|
const bucket = new mongodb.GridFSBucket(db.client.db());
|
||||||
bucket.delete((filename as unknown) as mongodb.ObjectId, () => {
|
const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
|
||||||
const uploadStream = bucket.openUploadStreamWithId(
|
metadata: metadata
|
||||||
filename,
|
});
|
||||||
filename,
|
|
||||||
{
|
|
||||||
metadata: metadata,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
uploadStream.on("error", (err) => {
|
uploadStream.on("error", err => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadStream.end(body, () => {
|
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 as unknown) as mongodb.ObjectId, (err) => {
|
bucket.delete((filename as unknown) as mongodb.ObjectId, err => {
|
||||||
if (err) {
|
if (err) return void throwError(err, response);
|
||||||
if (err.message.startsWith("FileNotFound")) {
|
|
||||||
response.writeHead(404);
|
|
||||||
response.end("404 Not Found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return void throwError(err, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
@@ -613,11 +593,7 @@ export function listener(
|
|||||||
const host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]);
|
const host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]);
|
||||||
ping(host, (err, res, stdout) => {
|
ping(host, (err, res, stdout) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (!res) {
|
if (!res) throwError(err, response);
|
||||||
response.writeHead(500, { Connection: "close" });
|
|
||||||
response.end(`${err.name}: ${err.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
response.writeHead(404, { "Cache-Control": "no-cache" });
|
response.writeHead(404, { "Cache-Control": "no-cache" });
|
||||||
response.end(`${err.name}: ${err.message}`);
|
response.end(`${err.name}: ${err.message}`);
|
||||||
return;
|
return;
|
||||||
@@ -625,7 +601,7 @@ export function listener(
|
|||||||
|
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
"Content-Type": "text/plain",
|
"Content-Type": "text/plain",
|
||||||
"Cache-Control": "no-cache",
|
"Cache-Control": "no-cache"
|
||||||
});
|
});
|
||||||
response.end(stdout);
|
response.end(stdout);
|
||||||
});
|
});
|
||||||
@@ -645,7 +621,7 @@ export function listener(
|
|||||||
response.writeHead(200);
|
response.writeHead(200);
|
||||||
response.end();
|
response.end();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throwError(err, response);
|
throwError(err, response);
|
||||||
});
|
});
|
||||||
@@ -693,15 +669,15 @@ export function listener(
|
|||||||
break;
|
break;
|
||||||
case "tasks":
|
case "tasks":
|
||||||
q = query.sanitizeQueryTypes(q, {
|
q = query.sanitizeQueryTypes(q, {
|
||||||
_id: (v) => new mongodb.ObjectID(v as string),
|
_id: v => new mongodb.ObjectID(v),
|
||||||
timestamp: (v) => new Date(v as number),
|
timestamp: v => new Date(v),
|
||||||
retries: Number,
|
retries: Number
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "faults":
|
case "faults":
|
||||||
q = query.sanitizeQueryTypes(q, {
|
q = query.sanitizeQueryTypes(q, {
|
||||||
timestamp: (v) => new Date(v as number),
|
timestamp: v => new Date(v),
|
||||||
retries: Number,
|
retries: Number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +713,7 @@ export function listener(
|
|||||||
|
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
total: total,
|
total: total
|
||||||
});
|
});
|
||||||
|
|
||||||
if (request.method === "HEAD") {
|
if (request.method === "HEAD") {
|
||||||
@@ -748,11 +724,11 @@ export function listener(
|
|||||||
response.write("[\n");
|
response.write("[\n");
|
||||||
i = 0;
|
i = 0;
|
||||||
cur.forEach(
|
cur.forEach(
|
||||||
(item) => {
|
item => {
|
||||||
if (i++) response.write(",\n");
|
if (i++) response.write(",\n");
|
||||||
response.write(JSON.stringify(item));
|
response.write(JSON.stringify(item));
|
||||||
},
|
},
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void throwError(err);
|
if (err) return void throwError(err);
|
||||||
response.end("\n]");
|
response.end("\n]");
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -20,7 +20,7 @@
|
|||||||
import { platform } from "os";
|
import { platform } from "os";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|
||||||
export interface PingResult {
|
interface Ping {
|
||||||
packetsTransmitted: number;
|
packetsTransmitted: number;
|
||||||
packetsReceived: number;
|
packetsReceived: number;
|
||||||
packetLoss: number;
|
packetLoss: number;
|
||||||
@@ -32,7 +32,7 @@ export interface PingResult {
|
|||||||
|
|
||||||
export function ping(
|
export function ping(
|
||||||
host: string,
|
host: string,
|
||||||
callback: (err: Error, res?: PingResult, stdout?: string) => void
|
callback: (err, res?, stdout?) => void
|
||||||
): void {
|
): void {
|
||||||
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
|
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
|
||||||
switch (platform()) {
|
switch (platform()) {
|
||||||
@@ -55,7 +55,7 @@ export function ping(
|
|||||||
}
|
}
|
||||||
|
|
||||||
exec(cmd, (err, stdout) => {
|
exec(cmd, (err, stdout) => {
|
||||||
let parsed: PingResult;
|
let parsed: Ping;
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
const m1 = stdout.match(parseRegExp1);
|
const m1 = stdout.match(parseRegExp1);
|
||||||
if (m1) {
|
if (m1) {
|
||||||
@@ -66,7 +66,7 @@ export function ping(
|
|||||||
min: +m1[4],
|
min: +m1[4],
|
||||||
avg: +m1[5],
|
avg: +m1[5],
|
||||||
max: +m1[6],
|
max: +m1[6],
|
||||||
mdev: +m1[7],
|
mdev: +m1[7]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const m2 = stdout.match(parseRegExp2);
|
const m2 = stdout.match(parseRegExp2);
|
||||||
@@ -78,7 +78,7 @@ export function ping(
|
|||||||
min: null,
|
min: null,
|
||||||
avg: null,
|
avg: null,
|
||||||
max: null,
|
max: null,
|
||||||
mdev: null,
|
mdev: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-18
@@ -17,9 +17,7 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function isObject(obj: any): boolean {
|
import * as common from "./common";
|
||||||
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToRegexp(input, flags?): RegExp | false {
|
function stringToRegexp(input, flags?): RegExp | false {
|
||||||
if (input.indexOf("*") === -1) return false;
|
if (input.indexOf("*") === -1) return false;
|
||||||
@@ -36,8 +34,8 @@ function stringToRegexp(input, flags?): RegExp | false {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalize(input): any {
|
function normalize(input): any {
|
||||||
if (typeof input === "string") {
|
if (common.typeOf(input) === common.STRING_TYPE) {
|
||||||
const vals: any = [input];
|
const vals = [input];
|
||||||
const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
|
const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
|
||||||
if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });
|
if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });
|
||||||
|
|
||||||
@@ -54,14 +52,14 @@ function normalize(input): any {
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandValue(value: unknown): unknown[] {
|
export function expandValue(value): any[] {
|
||||||
if (Array.isArray(value)) {
|
if (common.typeOf(value) === common.ARRAY_TYPE) {
|
||||||
let a = [];
|
let a = [];
|
||||||
for (const j of value) a = a.concat(expandValue(j));
|
for (const j of value) a = a.concat(expandValue(j));
|
||||||
return [a];
|
return [a];
|
||||||
} else if (!isObject(value)) {
|
} else if (common.typeOf(value) !== common.OBJECT_TYPE) {
|
||||||
const n = normalize(value);
|
const n = normalize(value);
|
||||||
if (!Array.isArray(n)) return [n];
|
if (common.typeOf(n) !== common.ARRAY_TYPE) return [n];
|
||||||
else return n;
|
else return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,14 +103,12 @@ function permute(param, val): any[] {
|
|||||||
return conditions;
|
return conditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expand(
|
export function expand(query): {} {
|
||||||
query: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const newQuery = {};
|
const newQuery = {};
|
||||||
for (const [k, v] of Object.entries(query)) {
|
for (const [k, v] of Object.entries(query)) {
|
||||||
if (k[0] === "$") {
|
if (k[0] === "$") {
|
||||||
// Operator
|
// Operator
|
||||||
newQuery[k] = (v as any[]).map((e) => expand(e));
|
newQuery[k] = (v as any[]).map(e => expand(e));
|
||||||
} else {
|
} else {
|
||||||
const conditions = permute(k, v);
|
const conditions = permute(k, v);
|
||||||
if (conditions.length > 1) {
|
if (conditions.length > 1) {
|
||||||
@@ -133,16 +129,100 @@ export function expand(
|
|||||||
return newQuery;
|
return newQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeQueryTypes(
|
function testExpressions(params, expressions, lop): boolean {
|
||||||
query: Record<string, unknown>,
|
for (const f of expressions) {
|
||||||
types: Record<string, (v: unknown) => unknown>
|
const res = test(params, f);
|
||||||
): Record<string, unknown> {
|
switch (lop) {
|
||||||
|
case "$and":
|
||||||
|
if (!res) return false;
|
||||||
|
break;
|
||||||
|
case "$or":
|
||||||
|
if (res) return true;
|
||||||
|
break;
|
||||||
|
case "$nor":
|
||||||
|
if (res) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lop) {
|
||||||
|
case "$and":
|
||||||
|
return true;
|
||||||
|
case "$or":
|
||||||
|
return false;
|
||||||
|
case "$nor":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown logical operator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test(params, query): boolean {
|
||||||
|
let res;
|
||||||
|
for (const [k, v] of Object.entries(query)) {
|
||||||
|
if (k.charAt(0) === "$") {
|
||||||
|
// Logical operator
|
||||||
|
res = testExpressions(params, v, k);
|
||||||
|
} else {
|
||||||
|
const value = params[k];
|
||||||
|
if (common.typeOf(v) !== common.OBJECT_TYPE) {
|
||||||
|
if (common.typeOf(value) === common.ARRAY_TYPE) {
|
||||||
|
// TODO comparing array to regex, array to array, and object to object
|
||||||
|
res = value.indexOf(v) !== -1;
|
||||||
|
} else {
|
||||||
|
if (common.typeOf(v) === common.REGEXP_TYPE)
|
||||||
|
res = (v as RegExp).test(value);
|
||||||
|
else res = v === value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const [k2, v2] of Object.entries(v)) {
|
||||||
|
switch (k2) {
|
||||||
|
case "$ne":
|
||||||
|
if (common.typeOf(value) === common.ARRAY_TYPE)
|
||||||
|
res = value.indexOf(v2) === -1;
|
||||||
|
else res = value !== v2;
|
||||||
|
break;
|
||||||
|
case "$lt":
|
||||||
|
res = value < v2;
|
||||||
|
break;
|
||||||
|
case "$lte":
|
||||||
|
res = value <= v2;
|
||||||
|
break;
|
||||||
|
case "$gt":
|
||||||
|
res = value > v2;
|
||||||
|
break;
|
||||||
|
case "$gte":
|
||||||
|
res = value >= v2;
|
||||||
|
break;
|
||||||
|
case "$regex":
|
||||||
|
res = v2.test(value);
|
||||||
|
break;
|
||||||
|
case "$in":
|
||||||
|
throw new Error("Operator not supported");
|
||||||
|
case "$nin":
|
||||||
|
throw new Error("Operator not supported");
|
||||||
|
case "$all":
|
||||||
|
throw new Error("Operator not supported");
|
||||||
|
case "$exists":
|
||||||
|
throw new Error("Operator not supported");
|
||||||
|
default:
|
||||||
|
throw new Error("Operator not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitizeQueryTypes(query, types): {} {
|
||||||
for (const [k, v] of Object.entries(query)) {
|
for (const [k, v] of Object.entries(query)) {
|
||||||
if (k[0] === "$") {
|
if (k[0] === "$") {
|
||||||
// Logical operator
|
// Logical operator
|
||||||
for (const vv of v as any[]) sanitizeQueryTypes(vv, types);
|
for (const vv of v as any[]) sanitizeQueryTypes(vv, types);
|
||||||
} else if (k in types) {
|
} else if (k in types) {
|
||||||
if (isObject(v)) {
|
if (common.typeOf(v) === common.OBJECT_TYPE) {
|
||||||
for (const [kk, vv] of Object.entries(v)) {
|
for (const [kk, vv] of Object.entries(v)) {
|
||||||
switch (kk) {
|
switch (kk) {
|
||||||
case "$in":
|
case "$in":
|
||||||
|
|||||||
+20
-30
@@ -109,16 +109,6 @@ class SandboxDate {
|
|||||||
|
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parse(dateString: string): number {
|
|
||||||
return Date.parse(dateString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UTC(
|
|
||||||
...args: [number, number?, number?, number?, number?, number?, number?]
|
|
||||||
): number {
|
|
||||||
return Date.UTC(...args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function random(): number {
|
function random(): number {
|
||||||
@@ -127,7 +117,7 @@ function random(): number {
|
|||||||
return state.rng();
|
return state.rng();
|
||||||
}
|
}
|
||||||
|
|
||||||
random.seed = function (s) {
|
random.seed = function(s) {
|
||||||
state.rng = seedrandom(s);
|
state.rng = seedrandom(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +125,7 @@ class ParameterWrapper {
|
|||||||
public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
|
public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
|
||||||
for (const attrName of attributes) {
|
for (const attrName of attributes) {
|
||||||
Object.defineProperty(this, attrName, {
|
Object.defineProperty(this, attrName, {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (state.uncommitted) commit();
|
if (state.uncommitted) commit();
|
||||||
|
|
||||||
if (state.revision !== unpackedRevision) {
|
if (state.revision !== unpackedRevision) {
|
||||||
@@ -157,12 +147,12 @@ class ParameterWrapper {
|
|||||||
if (!attr) return UNDEFINED;
|
if (!attr) return UNDEFINED;
|
||||||
|
|
||||||
return attr[1];
|
return attr[1];
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(this, "path", {
|
Object.defineProperty(this, "path", {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (state.uncommitted) commit();
|
if (state.uncommitted) commit();
|
||||||
|
|
||||||
if (state.revision !== unpackedRevision) {
|
if (state.revision !== unpackedRevision) {
|
||||||
@@ -177,11 +167,11 @@ class ParameterWrapper {
|
|||||||
if (!unpacked.length) return UNDEFINED;
|
if (!unpacked.length) return UNDEFINED;
|
||||||
|
|
||||||
return unpacked[0].toString();
|
return unpacked[0].toString();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, "size", {
|
Object.defineProperty(this, "size", {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (state.uncommitted) commit();
|
if (state.uncommitted) commit();
|
||||||
|
|
||||||
if (state.revision !== unpackedRevision) {
|
if (state.revision !== unpackedRevision) {
|
||||||
@@ -196,10 +186,10 @@ class ParameterWrapper {
|
|||||||
if (!unpacked.length) return UNDEFINED;
|
if (!unpacked.length) return UNDEFINED;
|
||||||
|
|
||||||
return unpacked.length;
|
return unpacked.length;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this[Symbol.iterator] = function* () {
|
this[Symbol.iterator] = function*() {
|
||||||
if (state.uncommitted) commit();
|
if (state.uncommitted) commit();
|
||||||
|
|
||||||
if (state.revision !== unpackedRevision) {
|
if (state.revision !== unpackedRevision) {
|
||||||
@@ -235,7 +225,7 @@ function declare(
|
|||||||
pathSet: null,
|
pathSet: null,
|
||||||
attrGet: null,
|
attrGet: null,
|
||||||
attrSet: null,
|
attrSet: null,
|
||||||
defer: true,
|
defer: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const attrs = new Set();
|
const attrs = new Set();
|
||||||
@@ -290,9 +280,9 @@ function commit(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ext(...args: unknown[]): any {
|
function ext(): any {
|
||||||
++state.extCounter;
|
++state.extCounter;
|
||||||
const extCall = args.map(String);
|
const extCall = Array.from(arguments).map(String);
|
||||||
const key = `${state.revision}: ${JSON.stringify(extCall)}`;
|
const key = `${state.revision}: ${JSON.stringify(extCall)}`;
|
||||||
|
|
||||||
if (key in state.sessionContext.extensionsCache)
|
if (key in state.sessionContext.extensionsCache)
|
||||||
@@ -302,11 +292,11 @@ function ext(...args: unknown[]): any {
|
|||||||
throw EXT;
|
throw EXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(msg: string, meta: Record<string, unknown>): void {
|
function log(msg: string, 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,
|
||||||
message: `Script: ${msg}`,
|
message: `Script: ${msg}`
|
||||||
});
|
});
|
||||||
|
|
||||||
delete details["hostname"];
|
delete details["hostname"];
|
||||||
@@ -342,8 +332,8 @@ function errorToFault(err: Error): Fault {
|
|||||||
message: err.message,
|
message: err.message,
|
||||||
detail: {
|
detail: {
|
||||||
name: err.name,
|
name: err.name,
|
||||||
message: err.message,
|
message: err.message
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (err.stack) {
|
if (err.stack) {
|
||||||
@@ -365,7 +355,7 @@ function errorToFault(err: Error): Fault {
|
|||||||
|
|
||||||
export async function run(
|
export async function run(
|
||||||
script: vm.Script,
|
script: vm.Script,
|
||||||
globals: Record<string, unknown>,
|
globals: {},
|
||||||
sessionContext: SessionContext,
|
sessionContext: SessionContext,
|
||||||
startRevision: number,
|
startRevision: number,
|
||||||
maxRevision: number,
|
maxRevision: number,
|
||||||
@@ -380,7 +370,7 @@ export async function run(
|
|||||||
extensions: {},
|
extensions: {},
|
||||||
clear: [],
|
clear: [],
|
||||||
rng: null,
|
rng: null,
|
||||||
extCounter: extCounter,
|
extCounter: extCounter
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const n of Object.keys(context)) delete context[n];
|
for (const n of Object.keys(context)) delete context[n];
|
||||||
@@ -403,7 +393,7 @@ export async function run(
|
|||||||
clear: null,
|
clear: null,
|
||||||
declare: null,
|
declare: null,
|
||||||
done: false,
|
done: false,
|
||||||
returnValue: null,
|
returnValue: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +413,7 @@ export async function run(
|
|||||||
clear: null,
|
clear: null,
|
||||||
declare: null,
|
declare: null,
|
||||||
done: false,
|
done: false,
|
||||||
returnValue: null,
|
returnValue: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +433,6 @@ export async function run(
|
|||||||
clear: _state.clear,
|
clear: _state.clear,
|
||||||
declare: _state.declarations,
|
declare: _state.declarations,
|
||||||
done: status === 0,
|
done: status === 0,
|
||||||
returnValue: ret,
|
returnValue: ret
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+10
-15
@@ -21,7 +21,10 @@ import * as crypto from "crypto";
|
|||||||
import * as later from "later";
|
import * as later from "later";
|
||||||
|
|
||||||
function md532(str): number {
|
function md532(str): number {
|
||||||
const digest = crypto.createHash("md5").update(str).digest();
|
const digest = crypto
|
||||||
|
.createHash("md5")
|
||||||
|
.update(str)
|
||||||
|
.digest();
|
||||||
return (
|
return (
|
||||||
digest.readUInt32LE(0) ^
|
digest.readUInt32LE(0) ^
|
||||||
digest.readUInt32LE(4) ^
|
digest.readUInt32LE(4) ^
|
||||||
@@ -30,37 +33,29 @@ function md532(str): number {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function variance(deviceId: string, vrnc: number): number {
|
export function variance(deviceId, vrnc): number {
|
||||||
return (md532(deviceId) >>> 0) % vrnc;
|
return (md532(deviceId) >>> 0) % vrnc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function interval(
|
export function interval(timestamp, intrvl, offset = 0): number {
|
||||||
timestamp: number,
|
|
||||||
intrvl: number,
|
|
||||||
offset = 0
|
|
||||||
): number {
|
|
||||||
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
|
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCron(cronExp: string): any {
|
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(
|
export function cron(timestamp, schedule, offset = 0): number[] {
|
||||||
timestamp: number,
|
|
||||||
schedule: unknown,
|
|
||||||
offset = 0
|
|
||||||
): number[] {
|
|
||||||
// TODO later.js doesn't throw erorr if expression is invalid!
|
// TODO later.js doesn't throw erorr if expression is invalid!
|
||||||
const ret = [0, 0];
|
const ret = [0, 0];
|
||||||
|
|
||||||
const prev = (schedule as any).prev(1, new Date(timestamp + offset));
|
const prev = schedule.prev(1, new Date(timestamp + offset));
|
||||||
if (prev) ret[0] = prev.setMilliseconds(0) - offset;
|
if (prev) ret[0] = prev.setMilliseconds(0) - offset;
|
||||||
|
|
||||||
const next = (schedule as any).next(1, new Date(timestamp + offset + 1000));
|
const next = schedule.next(1, new Date(timestamp + offset + 1000));
|
||||||
if (next) ret[1] = next.setMilliseconds(0) - offset;
|
if (next) ret[1] = next.setMilliseconds(0) - offset;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
+8
-9
@@ -20,7 +20,6 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import * as https from "https";
|
import * as https from "https";
|
||||||
import { Socket } from "net";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { ROOT_DIR } from "./config";
|
import { ROOT_DIR } from "./config";
|
||||||
|
|
||||||
@@ -53,12 +52,12 @@ function closeServer(timeout, callback): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function start(
|
export function start(
|
||||||
port: number,
|
port,
|
||||||
networkInterface: string,
|
networkInterface,
|
||||||
ssl: { key: string; cert: string },
|
ssl,
|
||||||
_listener: http.RequestListener,
|
_listener,
|
||||||
onConnection?: (socket: Socket) => void,
|
onConnection?,
|
||||||
keepAliveTimeout = -1
|
keepAliveTimeout: number = -1
|
||||||
): void {
|
): void {
|
||||||
listener = _listener;
|
listener = _listener;
|
||||||
|
|
||||||
@@ -66,10 +65,10 @@ export function start(
|
|||||||
const options = {
|
const options = {
|
||||||
key: ssl.key
|
key: ssl.key
|
||||||
.split(":")
|
.split(":")
|
||||||
.map((f) => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))),
|
.map(f => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))),
|
||||||
cert: ssl.cert
|
cert: ssl.cert
|
||||||
.split(":")
|
.split(":")
|
||||||
.map((f) => fs.readFileSync(path.resolve(ROOT_DIR, f.trim()))),
|
.map(f => fs.readFileSync(path.resolve(ROOT_DIR, f.trim())))
|
||||||
};
|
};
|
||||||
|
|
||||||
server = https.createServer(options, listener);
|
server = https.createServer(options, listener);
|
||||||
|
|||||||
+419
-521
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,745 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
|
*
|
||||||
|
* This file is part of GenieACS.
|
||||||
|
*
|
||||||
|
* GenieACS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* GenieACS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { parseXml, Document, Element, ParserOptions } from "libxmljs";
|
||||||
|
import * as config from "./config";
|
||||||
|
import { version as VERSION } from "../package.json";
|
||||||
|
import {
|
||||||
|
CpeGetResponse,
|
||||||
|
CpeSetResponse,
|
||||||
|
InformRequest,
|
||||||
|
AcsResponse,
|
||||||
|
CpeRequest,
|
||||||
|
FaultStruct,
|
||||||
|
SpvFault,
|
||||||
|
CpeFault,
|
||||||
|
SoapMessage,
|
||||||
|
TransferCompleteRequest
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
const SERVER_NAME = `GenieACS/${VERSION}`;
|
||||||
|
|
||||||
|
const NAMESPACES = {
|
||||||
|
"1.0": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
cwmp: "urn:dslforum-org:cwmp-1-0"
|
||||||
|
},
|
||||||
|
"1.1": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
cwmp: "urn:dslforum-org:cwmp-1-1"
|
||||||
|
},
|
||||||
|
"1.2": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
cwmp: "urn:dslforum-org:cwmp-1-2"
|
||||||
|
},
|
||||||
|
"1.3": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
cwmp: "urn:dslforum-org:cwmp-1-2"
|
||||||
|
},
|
||||||
|
"1.4": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
cwmp: "urn:dslforum-org:cwmp-1-3"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIBXMLJS_PARSE_OPTIONS: ParserOptions = {
|
||||||
|
nocdata: true,
|
||||||
|
recover: !!config.get("XML_RECOVER"),
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/camelcase */
|
||||||
|
ignore_enc: !!config.get("XML_IGNORE_ENC")
|
||||||
|
};
|
||||||
|
|
||||||
|
interface WriterOptions {
|
||||||
|
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"),
|
||||||
|
selfCloseEmpty: !config.get("XML_NO_EMPTY")
|
||||||
|
};
|
||||||
|
|
||||||
|
let warnings;
|
||||||
|
|
||||||
|
function parseBool(v): boolean {
|
||||||
|
v = "" + v;
|
||||||
|
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
||||||
|
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
||||||
|
return false;
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function event(xml): string[] {
|
||||||
|
return xml
|
||||||
|
.childNodes()
|
||||||
|
.filter(n => n.name() === "EventStruct")
|
||||||
|
.map(c =>
|
||||||
|
c
|
||||||
|
.childNodes()
|
||||||
|
.find(n => n.name() === "EventCode")
|
||||||
|
.text()
|
||||||
|
.trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parameterInfoList(xml): [string, boolean][] {
|
||||||
|
return xml
|
||||||
|
.childNodes()
|
||||||
|
.filter(e => e.name() === "ParameterInfoStruct")
|
||||||
|
.map(e => {
|
||||||
|
let param, value;
|
||||||
|
for (const c of e.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "Name":
|
||||||
|
param = c.text();
|
||||||
|
break;
|
||||||
|
case "Writable":
|
||||||
|
value = c.text();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = parseBool(value);
|
||||||
|
|
||||||
|
if (parsed === null) {
|
||||||
|
warnings.push({
|
||||||
|
message: "Invalid writable attribute",
|
||||||
|
parameter: param
|
||||||
|
});
|
||||||
|
parsed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [param, parsed];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parameterValueList(
|
||||||
|
xml
|
||||||
|
): [string, string | number | boolean, string][] {
|
||||||
|
return xml
|
||||||
|
.childNodes()
|
||||||
|
.filter(e => e.name() === "ParameterValueStruct")
|
||||||
|
.map(e => {
|
||||||
|
let valueElement, param;
|
||||||
|
for (const c of e.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "Name":
|
||||||
|
param = c.text();
|
||||||
|
break;
|
||||||
|
case "Value":
|
||||||
|
valueElement = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueType = valueElement.attr("type").value();
|
||||||
|
const value = valueElement.text();
|
||||||
|
let parsed = value;
|
||||||
|
if (valueType === "xsd:boolean") {
|
||||||
|
parsed = parseBool(value);
|
||||||
|
if (parsed === null) {
|
||||||
|
warnings.push({
|
||||||
|
message: "Invalid value attribute",
|
||||||
|
parameter: param
|
||||||
|
});
|
||||||
|
parsed = value;
|
||||||
|
}
|
||||||
|
} else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") {
|
||||||
|
parsed = parseInt(value);
|
||||||
|
if (isNaN(parsed)) {
|
||||||
|
warnings.push({
|
||||||
|
message: "Invalid value attribute",
|
||||||
|
parameter: param
|
||||||
|
});
|
||||||
|
parsed = value;
|
||||||
|
}
|
||||||
|
} else if (valueType === "xsd:dateTime") {
|
||||||
|
parsed = Date.parse(value);
|
||||||
|
if (isNaN(parsed)) {
|
||||||
|
warnings.push({
|
||||||
|
message: "Invalid value attribute",
|
||||||
|
parameter: param
|
||||||
|
});
|
||||||
|
parsed = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [param, parsed, valueType];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetParameterNames(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:GetParameterNames");
|
||||||
|
el.node("ParameterPath").text(methodRequest.parameterPath);
|
||||||
|
el.node("NextLevel").text(+methodRequest.nextLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetParameterNamesResponse(xml): CpeGetResponse {
|
||||||
|
return {
|
||||||
|
name: "GetParameterNamesResponse",
|
||||||
|
parameterList: parameterInfoList(
|
||||||
|
xml.childNodes().find(n => n.name() === "ParameterList")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetParameterValues(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:GetParameterValues").node("ParameterNames");
|
||||||
|
el.attr({
|
||||||
|
"soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const p of methodRequest.parameterNames) el.node("string").text(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetParameterValuesResponse(xml): CpeGetResponse {
|
||||||
|
return {
|
||||||
|
name: "GetParameterValuesResponse",
|
||||||
|
parameterList: parameterValueList(
|
||||||
|
xml.childNodes().find(n => n.name() === "ParameterList")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetParameterValues(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:SetParameterValues");
|
||||||
|
const paramList = el.node("ParameterList");
|
||||||
|
paramList.attr({
|
||||||
|
"soap-enc:arrayType": `cwmp:ParameterValueStruct[${methodRequest.parameterList.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const p of methodRequest.parameterList) {
|
||||||
|
const pvs = paramList.node("ParameterValueStruct");
|
||||||
|
pvs.node("Name").text(p[0]);
|
||||||
|
let val = p[1];
|
||||||
|
if (p[2] === "xsd:dateTime" && typeof val === "number") {
|
||||||
|
val = new Date(val).toISOString();
|
||||||
|
if (methodRequest.DATETIME_MILLISECONDS === false)
|
||||||
|
val = val.replace(".000", "");
|
||||||
|
}
|
||||||
|
if (p[2] === "xsd:boolean" && typeof val === "boolean")
|
||||||
|
if (methodRequest.BOOLEAN_LITERAL === false) val = +val;
|
||||||
|
|
||||||
|
pvs
|
||||||
|
.node("Value")
|
||||||
|
.attr({ "xsi:type": p[2] })
|
||||||
|
.text(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetParameterValuesResponse(xml): CpeSetResponse {
|
||||||
|
return {
|
||||||
|
name: "SetParameterValuesResponse",
|
||||||
|
status: parseInt(
|
||||||
|
xml
|
||||||
|
.childNodes()
|
||||||
|
.find(n => n.name() === "Status")
|
||||||
|
.text()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddObject(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:AddObject");
|
||||||
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddObjectResponse(xml): CpeSetResponse {
|
||||||
|
let instanceNumber, status;
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "InstanceNumber":
|
||||||
|
instanceNumber = parseInt(c.text());
|
||||||
|
break;
|
||||||
|
case "Status":
|
||||||
|
status = parseInt(c.text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "AddObjectResponse",
|
||||||
|
instanceNumber: instanceNumber,
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteObject(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:DeleteObject");
|
||||||
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteObjectResponse(xml): CpeSetResponse {
|
||||||
|
return {
|
||||||
|
name: "DeleteObjectResponse",
|
||||||
|
status: parseInt(
|
||||||
|
xml
|
||||||
|
.childNodes()
|
||||||
|
.find(n => n.name() === "Status")
|
||||||
|
.text()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function Reboot(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:Reboot");
|
||||||
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function RebootResponse(): CpeSetResponse {
|
||||||
|
return {
|
||||||
|
name: "RebootResponse"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function FactoryReset(xml): void {
|
||||||
|
xml.node("cwmp:FactoryReset");
|
||||||
|
}
|
||||||
|
|
||||||
|
function FactoryResetResponse(): CpeSetResponse {
|
||||||
|
return {
|
||||||
|
name: "FactoryResetResponse"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function Download(xml, methodRequest): void {
|
||||||
|
const el = xml.node("cwmp:Download");
|
||||||
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
|
el.node("FileType").text(methodRequest.fileType);
|
||||||
|
el.node("URL").text(methodRequest.url);
|
||||||
|
el.node("Username").text(methodRequest.username || "");
|
||||||
|
el.node("Password").text(methodRequest.password || "");
|
||||||
|
el.node("FileSize").text(methodRequest.fileSize || "0");
|
||||||
|
el.node("TargetFileName").text(methodRequest.targetFileName || "");
|
||||||
|
el.node("DelaySeconds").text(methodRequest.delaySeconds || "0");
|
||||||
|
el.node("SuccessURL").text(methodRequest.successUrl || "");
|
||||||
|
el.node("FailureURL").text(methodRequest.failureUrl || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function DownloadResponse(xml): CpeSetResponse {
|
||||||
|
let status, startTime, completeTime;
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "Status":
|
||||||
|
status = parseInt(c.text());
|
||||||
|
break;
|
||||||
|
case "StartTime":
|
||||||
|
startTime = Date.parse(c.text());
|
||||||
|
break;
|
||||||
|
case "CompleteTime":
|
||||||
|
completeTime = Date.parse(c.text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "DownloadResponse",
|
||||||
|
status: status,
|
||||||
|
startTime: startTime,
|
||||||
|
completeTime: completeTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function Inform(xml): InformRequest {
|
||||||
|
let retryCount, evnt, parameterList;
|
||||||
|
const deviceId = {
|
||||||
|
Manufacturer: null,
|
||||||
|
OUI: null,
|
||||||
|
ProductClass: null,
|
||||||
|
SerialNumber: null
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "ParameterList":
|
||||||
|
parameterList = parameterValueList(c);
|
||||||
|
break;
|
||||||
|
case "DeviceId":
|
||||||
|
for (const cc of c.childNodes()) {
|
||||||
|
const n = cc.name();
|
||||||
|
if (n in deviceId) deviceId[n] = cc.text();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Event":
|
||||||
|
evnt = event(c);
|
||||||
|
break;
|
||||||
|
case "RetryCount":
|
||||||
|
retryCount = parseInt(c.text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Inform",
|
||||||
|
parameterList: parameterList,
|
||||||
|
deviceId: deviceId,
|
||||||
|
event: evnt,
|
||||||
|
retryCount: retryCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function InformResponse(xml): void {
|
||||||
|
xml
|
||||||
|
.node("cwmp:InformResponse")
|
||||||
|
.node("MaxEnvelopes")
|
||||||
|
.text(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetRPCMethods(): AcsResponse {
|
||||||
|
return { name: "GetRPCMethods" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetRPCMethodsResponse(xml, methodResponse): void {
|
||||||
|
const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList");
|
||||||
|
el.attr({
|
||||||
|
"soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const m of methodResponse.methodList) el.node("string").text(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransferComplete(xml): TransferCompleteRequest {
|
||||||
|
let commandKey, _faultStruct, startTime, completeTime;
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "CommandKey":
|
||||||
|
commandKey = c.text();
|
||||||
|
break;
|
||||||
|
case "FaultStruct":
|
||||||
|
_faultStruct = faultStruct(c);
|
||||||
|
break;
|
||||||
|
case "StartTime":
|
||||||
|
startTime = Date.parse(c.text());
|
||||||
|
break;
|
||||||
|
case "CompleteTime":
|
||||||
|
completeTime = Date.parse(c.text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "TransferComplete",
|
||||||
|
commandKey: commandKey,
|
||||||
|
faultStruct: _faultStruct,
|
||||||
|
startTime: startTime,
|
||||||
|
completeTime: completeTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransferCompleteResponse(xml): void {
|
||||||
|
xml.node("cwmp:TransferCompleteResponse").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function RequestDownload(xml): CpeRequest {
|
||||||
|
return {
|
||||||
|
name: "RequestDownload",
|
||||||
|
fileType: xml
|
||||||
|
.childNodes()
|
||||||
|
.find(n => n.name() === "FileType")
|
||||||
|
.text()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function RequestDownloadResponse(xml): void {
|
||||||
|
xml.node("cwmp:RequestDownloadResponse").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function faultStruct(xml): FaultStruct {
|
||||||
|
let faultCode, faultString, setParameterValuesFault: SpvFault[], pn, fc, fs;
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "FaultCode":
|
||||||
|
faultCode = c.text();
|
||||||
|
break;
|
||||||
|
case "FaultString":
|
||||||
|
faultString = c.text();
|
||||||
|
break;
|
||||||
|
case "SetParameterValuesFault":
|
||||||
|
setParameterValuesFault = setParameterValuesFault || [];
|
||||||
|
for (const cc of c.childNodes()) {
|
||||||
|
switch (cc.name()) {
|
||||||
|
case "ParameterName":
|
||||||
|
pn = cc.text();
|
||||||
|
break;
|
||||||
|
case "FaultCode":
|
||||||
|
fc = cc.text();
|
||||||
|
break;
|
||||||
|
case "FaultString":
|
||||||
|
fs = cc.text();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setParameterValuesFault.push({
|
||||||
|
parameterName: pn,
|
||||||
|
faultCode: fc,
|
||||||
|
faultString: fs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { faultCode, faultString, setParameterValuesFault };
|
||||||
|
}
|
||||||
|
|
||||||
|
function fault(xml): CpeFault {
|
||||||
|
let faultCode, faultString, detail;
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "faultcode":
|
||||||
|
faultCode = c.text();
|
||||||
|
break;
|
||||||
|
case "faultstring":
|
||||||
|
faultString = c.text();
|
||||||
|
break;
|
||||||
|
case "detail":
|
||||||
|
detail = faultStruct(c.childNodes().find(n => n.name() === "Fault"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { faultCode, faultString, detail };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function request(data, cwmpVersion, warn): SoapMessage {
|
||||||
|
warnings = warn;
|
||||||
|
|
||||||
|
const rpc = {
|
||||||
|
id: null,
|
||||||
|
cwmpVersion: cwmpVersion,
|
||||||
|
sessionTimeout: null,
|
||||||
|
cpeRequest: null,
|
||||||
|
cpeFault: null,
|
||||||
|
cpeResponse: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!data.length) return rpc;
|
||||||
|
|
||||||
|
const xml = parseXml(data, LIBXMLJS_PARSE_OPTIONS);
|
||||||
|
|
||||||
|
let headerElement, bodyElement;
|
||||||
|
|
||||||
|
for (const c of xml.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "Header":
|
||||||
|
headerElement = c;
|
||||||
|
break;
|
||||||
|
case "Body":
|
||||||
|
bodyElement = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
for (const c of headerElement.childNodes()) {
|
||||||
|
switch (c.name()) {
|
||||||
|
case "ID":
|
||||||
|
rpc.id = c.text();
|
||||||
|
break;
|
||||||
|
case "sessionTimeout":
|
||||||
|
rpc.sessionTimeout = parseInt(c.text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const methodElement of bodyElement.childNodes()) {
|
||||||
|
const methodName = methodElement.name();
|
||||||
|
if (methodName === "text") continue;
|
||||||
|
|
||||||
|
if (!rpc.cwmpVersion && methodName !== "Fault") {
|
||||||
|
switch (methodElement.namespace().href()) {
|
||||||
|
case "urn:dslforum-org:cwmp-1-0":
|
||||||
|
rpc.cwmpVersion = "1.0";
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-1":
|
||||||
|
rpc.cwmpVersion = "1.1";
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-2":
|
||||||
|
if (rpc.sessionTimeout) rpc.cwmpVersion = "1.3";
|
||||||
|
else rpc.cwmpVersion = "1.2";
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-3":
|
||||||
|
rpc.cwmpVersion = "1.4";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unrecognized CWMP version");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (methodName) {
|
||||||
|
case "Inform":
|
||||||
|
rpc.cpeRequest = Inform(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetRPCMethods":
|
||||||
|
rpc.cpeRequest = GetRPCMethods();
|
||||||
|
break;
|
||||||
|
case "TransferComplete":
|
||||||
|
rpc.cpeRequest = TransferComplete(methodElement);
|
||||||
|
break;
|
||||||
|
case "RequestDownload":
|
||||||
|
rpc.cpeRequest = RequestDownload(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetParameterNamesResponse":
|
||||||
|
rpc.cpeResponse = GetParameterNamesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetParameterValuesResponse":
|
||||||
|
rpc.cpeResponse = GetParameterValuesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "SetParameterValuesResponse":
|
||||||
|
rpc.cpeResponse = SetParameterValuesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "AddObjectResponse":
|
||||||
|
rpc.cpeResponse = AddObjectResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "DeleteObjectResponse":
|
||||||
|
rpc.cpeResponse = DeleteObjectResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "RebootResponse":
|
||||||
|
rpc.cpeResponse = RebootResponse();
|
||||||
|
break;
|
||||||
|
case "FactoryResetResponse":
|
||||||
|
rpc.cpeResponse = FactoryResetResponse();
|
||||||
|
break;
|
||||||
|
case "DownloadResponse":
|
||||||
|
rpc.cpeResponse = DownloadResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "Fault":
|
||||||
|
rpc.cpeFault = fault(methodElement);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`8000 Method not supported ${methodName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSoapEnv(cwmpVersion: string): Element {
|
||||||
|
const xml = new Document();
|
||||||
|
const env = xml.node("soap-env:Envelope");
|
||||||
|
|
||||||
|
for (const prefix of Object.keys(NAMESPACES[cwmpVersion]))
|
||||||
|
env.defineNamespace(prefix, NAMESPACES[cwmpVersion][prefix]);
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function response(rpc): { code: number; headers: {}; data: string } {
|
||||||
|
const headers = {
|
||||||
|
Server: SERVER_NAME,
|
||||||
|
SOAPServer: SERVER_NAME
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!rpc) return { code: 204, headers: headers, data: "" };
|
||||||
|
|
||||||
|
const env = createSoapEnv(rpc.cwmpVersion);
|
||||||
|
const header = env.node("soap-env:Header");
|
||||||
|
header
|
||||||
|
.node("cwmp:ID")
|
||||||
|
.attr({
|
||||||
|
"soap-env:mustUnderstand": "1"
|
||||||
|
})
|
||||||
|
.text(rpc.id);
|
||||||
|
|
||||||
|
const body = env.node("soap-env:Body");
|
||||||
|
|
||||||
|
if (rpc.acsResponse) {
|
||||||
|
switch (rpc.acsResponse.name) {
|
||||||
|
case "InformResponse":
|
||||||
|
InformResponse(body);
|
||||||
|
break;
|
||||||
|
case "GetRPCMethodsResponse":
|
||||||
|
GetRPCMethodsResponse(body, rpc.acsResponse);
|
||||||
|
break;
|
||||||
|
case "TransferCompleteResponse":
|
||||||
|
TransferCompleteResponse(body);
|
||||||
|
break;
|
||||||
|
case "RequestDownloadResponse":
|
||||||
|
RequestDownloadResponse(body);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
|
||||||
|
}
|
||||||
|
} else if (rpc.acsRequest) {
|
||||||
|
switch (rpc.acsRequest.name) {
|
||||||
|
case "GetParameterNames":
|
||||||
|
GetParameterNames(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "GetParameterValues":
|
||||||
|
GetParameterValues(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "SetParameterValues":
|
||||||
|
SetParameterValues(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "AddObject":
|
||||||
|
AddObject(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "DeleteObject":
|
||||||
|
DeleteObject(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "Reboot":
|
||||||
|
Reboot(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "FactoryReset":
|
||||||
|
FactoryReset(body);
|
||||||
|
break;
|
||||||
|
case "Download":
|
||||||
|
Download(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown method request ${rpc.acsRequest.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers["Content-Type"] = 'text/xml; charset="utf-8"';
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
headers: headers,
|
||||||
|
data: env.doc().toString(LIBXMLJS_SAVE_OPTIONS)
|
||||||
|
};
|
||||||
|
}
|
||||||
+119
-201
@@ -22,34 +22,22 @@ import {
|
|||||||
Element,
|
Element,
|
||||||
parseAttrs,
|
parseAttrs,
|
||||||
encodeEntities,
|
encodeEntities,
|
||||||
decodeEntities,
|
decodeEntities
|
||||||
} from "./xml-parser";
|
} from "./xml-parser";
|
||||||
import memoize from "./common/memoize";
|
import memoize from "./common/memoize";
|
||||||
import { version as VERSION } from "../package.json";
|
import { version as VERSION } from "../package.json";
|
||||||
import {
|
import {
|
||||||
|
CpeGetResponse,
|
||||||
|
CpeSetResponse,
|
||||||
InformRequest,
|
InformRequest,
|
||||||
|
AcsResponse,
|
||||||
|
CpeRequest,
|
||||||
FaultStruct,
|
FaultStruct,
|
||||||
SpvFault,
|
SpvFault,
|
||||||
CpeFault,
|
CpeFault,
|
||||||
SoapMessage,
|
SoapMessage,
|
||||||
TransferCompleteRequest,
|
TransferCompleteRequest
|
||||||
AcsRequest,
|
|
||||||
GetParameterNamesResponse,
|
|
||||||
GetParameterValuesResponse,
|
|
||||||
GetParameterAttributesResponse,
|
|
||||||
SetParameterValuesResponse,
|
|
||||||
SetParameterAttributesResponse,
|
|
||||||
AddObjectResponse,
|
|
||||||
DeleteObject,
|
|
||||||
DeleteObjectResponse,
|
|
||||||
RebootResponse,
|
|
||||||
FactoryResetResponse,
|
|
||||||
DownloadResponse,
|
|
||||||
GetRPCMethodsRequest,
|
|
||||||
RequestDownloadRequest,
|
|
||||||
AcsResponse,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import Path from "./common/path";
|
|
||||||
|
|
||||||
const SERVER_NAME = `GenieACS/${VERSION}`;
|
const SERVER_NAME = `GenieACS/${VERSION}`;
|
||||||
|
|
||||||
@@ -59,58 +47,60 @@ const NAMESPACES = {
|
|||||||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema",
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
cwmp: "urn:dslforum-org:cwmp-1-0",
|
cwmp: "urn:dslforum-org:cwmp-1-0"
|
||||||
},
|
},
|
||||||
"1.1": {
|
"1.1": {
|
||||||
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema",
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
cwmp: "urn:dslforum-org:cwmp-1-1",
|
cwmp: "urn:dslforum-org:cwmp-1-1"
|
||||||
},
|
},
|
||||||
"1.2": {
|
"1.2": {
|
||||||
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema",
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
cwmp: "urn:dslforum-org:cwmp-1-2",
|
cwmp: "urn:dslforum-org:cwmp-1-2"
|
||||||
},
|
},
|
||||||
"1.3": {
|
"1.3": {
|
||||||
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema",
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
cwmp: "urn:dslforum-org:cwmp-1-2",
|
cwmp: "urn:dslforum-org:cwmp-1-2"
|
||||||
},
|
},
|
||||||
"1.4": {
|
"1.4": {
|
||||||
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema",
|
xsd: "http://www.w3.org/2001/XMLSchema",
|
||||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
cwmp: "urn:dslforum-org:cwmp-1-3",
|
cwmp: "urn:dslforum-org:cwmp-1-3"
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let warnings: Record<string, unknown>[];
|
let warnings;
|
||||||
|
|
||||||
const memoizedParseAttrs = memoize(parseAttrs);
|
const memoizedParseAttrs = memoize(parseAttrs);
|
||||||
|
|
||||||
function parseBool(v: string): boolean {
|
function parseBool(v): boolean {
|
||||||
if (v === "true" || v === "1") return true;
|
v = "" + v;
|
||||||
if (v === "false" || v === "0") return false;
|
if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
|
||||||
return null;
|
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
||||||
|
return false;
|
||||||
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function event(xml: Element): string[] {
|
function event(xml: Element): string[] {
|
||||||
return xml.children
|
return xml.children
|
||||||
.filter((n) => n.localName === "EventStruct")
|
.filter(n => n.localName === "EventStruct")
|
||||||
.map((c) => c.children.find((n) => n.localName === "EventCode").text);
|
.map(c => c.children.find(n => n.localName === "EventCode").text.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function parameterInfoList(xml: Element): [Path, boolean, boolean][] {
|
function parameterInfoList(xml: Element): [string, boolean][] {
|
||||||
return xml.children
|
return xml.children
|
||||||
.filter((e) => e.localName === "ParameterInfoStruct")
|
.filter(e => e.localName === "ParameterInfoStruct")
|
||||||
.map<[Path, boolean, boolean]>((e) => {
|
.map<[string, boolean]>(e => {
|
||||||
let param: string, value: string;
|
let param: string, value: string;
|
||||||
for (const c of e.children) {
|
for (const c of e.children) {
|
||||||
switch (c.localName) {
|
switch (c.localName) {
|
||||||
@@ -125,33 +115,30 @@ function parameterInfoList(xml: Element): [Path, boolean, boolean][] {
|
|||||||
|
|
||||||
let parsed: boolean = parseBool(value);
|
let parsed: boolean = parseBool(value);
|
||||||
|
|
||||||
if (parsed == null) {
|
if (parsed === null) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
message: "Invalid writable attribute",
|
message: "Invalid writable attribute",
|
||||||
parameter: param,
|
parameter: param
|
||||||
});
|
});
|
||||||
parsed = false;
|
parsed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param && !param.endsWith("."))
|
return [param, parsed];
|
||||||
return [Path.parse(param), false, parsed];
|
|
||||||
else return [Path.parse(param.slice(0, -1)), true, parsed];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValueType = memoize((str: string) => {
|
const getValueType = memoize((str: string) =>
|
||||||
const attrs = parseAttrs(str);
|
parseAttrs(str)
|
||||||
for (const attr of attrs) if (attr.localName === "type") return attr.value;
|
.find(s => s.localName === "type")
|
||||||
|
.value.trim()
|
||||||
return null;
|
);
|
||||||
});
|
|
||||||
|
|
||||||
function parameterValueList(
|
function parameterValueList(
|
||||||
xml: Element
|
xml: Element
|
||||||
): [Path, string | number | boolean, string][] {
|
): [string, string | number | boolean, string][] {
|
||||||
return xml.children
|
return xml.children
|
||||||
.filter((e) => e.localName === "ParameterValueStruct")
|
.filter(e => e.localName === "ParameterValueStruct")
|
||||||
.map<[Path, string | number | boolean, string]>((e) => {
|
.map<[string, string | number | boolean, string]>(e => {
|
||||||
let valueElement: Element, param: string;
|
let valueElement: Element, param: string;
|
||||||
for (const c of e.children) {
|
for (const c of e.children) {
|
||||||
switch (c.localName) {
|
switch (c.localName) {
|
||||||
@@ -164,23 +151,16 @@ function parameterValueList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let valueType = getValueType(valueElement.attrs);
|
const valueType = getValueType(valueElement.attrs);
|
||||||
if (!valueType) {
|
|
||||||
warnings.push({
|
|
||||||
message: "Invalid value type attribute",
|
|
||||||
parameter: param,
|
|
||||||
});
|
|
||||||
valueType = "xsd:string";
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = decodeEntities(valueElement.text);
|
const value = decodeEntities(valueElement.text);
|
||||||
let parsed: string | number | boolean = value;
|
let parsed: string | number | boolean = value;
|
||||||
if (valueType === "xsd:boolean") {
|
if (valueType === "xsd:boolean") {
|
||||||
parsed = parseBool(value);
|
parsed = parseBool(value);
|
||||||
if (parsed == null) {
|
if (parsed === null) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
message: "Invalid value attribute",
|
message: "Invalid value attribute",
|
||||||
parameter: param,
|
parameter: param
|
||||||
});
|
});
|
||||||
parsed = value;
|
parsed = value;
|
||||||
}
|
}
|
||||||
@@ -189,7 +169,7 @@ function parameterValueList(
|
|||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
message: "Invalid value attribute",
|
message: "Invalid value attribute",
|
||||||
parameter: param,
|
parameter: param
|
||||||
});
|
});
|
||||||
parsed = value;
|
parsed = value;
|
||||||
}
|
}
|
||||||
@@ -198,20 +178,20 @@ function parameterValueList(
|
|||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
message: "Invalid value attribute",
|
message: "Invalid value attribute",
|
||||||
parameter: param,
|
parameter: param
|
||||||
});
|
});
|
||||||
parsed = value;
|
parsed = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [Path.parse(param), parsed, valueType];
|
return [param, parsed, valueType];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parameterAttributeList(xml: Element): [Path, number, string[]][] {
|
function parameterAttributeList(xml: Element): [string, number, string[]][] {
|
||||||
return xml.children
|
return xml.children
|
||||||
.filter((e) => e.localName === "ParameterAttributeStruct")
|
.filter(e => e.localName === "ParameterAttributeStruct")
|
||||||
.map<[Path, number, string[]]>((e) => {
|
.map<[string, number, string[]]>(e => {
|
||||||
let notificationElement: Element,
|
let notificationElement: Element,
|
||||||
accessListElement: Element,
|
accessListElement: Element,
|
||||||
param: string;
|
param: string;
|
||||||
@@ -229,20 +209,21 @@ function parameterAttributeList(xml: Element): [Path, number, string[]][] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let notification = parseInt(notificationElement.text);
|
const notification = decodeEntities(notificationElement.text);
|
||||||
if (isNaN(notification)) {
|
const notif = +notification;
|
||||||
|
|
||||||
|
if (notif !== parseInt(notification)) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
message: "Invalid notification attribute",
|
message: "Invalid notification attribute",
|
||||||
parameter: param,
|
parameter: param
|
||||||
});
|
});
|
||||||
notification = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessList = accessListElement.children
|
const accessList = accessListElement.children
|
||||||
.filter((c) => c.localName === "string")
|
.filter(c => c.localName === "string")
|
||||||
.map((c) => decodeEntities(c.text));
|
.map(c => decodeEntities(c.text));
|
||||||
|
|
||||||
return [Path.parse(param), notification, accessList];
|
return [param, notif, accessList];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,12 +233,12 @@ function GetParameterNames(methodRequest): string {
|
|||||||
}</ParameterPath><NextLevel>${+methodRequest.nextLevel}</NextLevel></cwmp:GetParameterNames>`;
|
}</ParameterPath><NextLevel>${+methodRequest.nextLevel}</NextLevel></cwmp:GetParameterNames>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterNamesResponse(xml): GetParameterNamesResponse {
|
function GetParameterNamesResponse(xml): CpeGetResponse {
|
||||||
return {
|
return {
|
||||||
name: "GetParameterNamesResponse",
|
name: "GetParameterNamesResponse",
|
||||||
parameterList: parameterInfoList(
|
parameterList: parameterInfoList(
|
||||||
xml.children.find((n) => n.localName === "ParameterList")
|
xml.children.find(n => n.localName === "ParameterList")
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,16 +246,16 @@ function GetParameterValues(methodRequest): string {
|
|||||||
return `<cwmp:GetParameterValues><ParameterNames soap-enc:arrayType="xsd:string[${
|
return `<cwmp:GetParameterValues><ParameterNames soap-enc:arrayType="xsd:string[${
|
||||||
methodRequest.parameterNames.length
|
methodRequest.parameterNames.length
|
||||||
}]">${methodRequest.parameterNames
|
}]">${methodRequest.parameterNames
|
||||||
.map((p) => `<string>${p}</string>`)
|
.map(p => `<string>${p}</string>`)
|
||||||
.join("")}</ParameterNames></cwmp:GetParameterValues>`;
|
.join("")}</ParameterNames></cwmp:GetParameterValues>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterValuesResponse(xml: Element): GetParameterValuesResponse {
|
function GetParameterValuesResponse(xml: Element): CpeGetResponse {
|
||||||
return {
|
return {
|
||||||
name: "GetParameterValuesResponse",
|
name: "GetParameterValuesResponse",
|
||||||
parameterList: parameterValueList(
|
parameterList: parameterValueList(
|
||||||
xml.children.find((n) => n.localName === "ParameterList")
|
xml.children.find(n => n.localName === "ParameterList")
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,23 +263,21 @@ function GetParameterAttributes(methodRequest): string {
|
|||||||
return `<cwmp:GetParameterAttributes><ParameterNames soap-enc:arrayType="xsd:string[${
|
return `<cwmp:GetParameterAttributes><ParameterNames soap-enc:arrayType="xsd:string[${
|
||||||
methodRequest.parameterNames.length
|
methodRequest.parameterNames.length
|
||||||
}]">${methodRequest.parameterNames
|
}]">${methodRequest.parameterNames
|
||||||
.map((p) => `<string>${p}</string>`)
|
.map(p => `<string>${p}</string>`)
|
||||||
.join("")}</ParameterNames></cwmp:GetParameterAttributes>`;
|
.join("")}</ParameterNames></cwmp:GetParameterAttributes>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetParameterAttributesResponse(
|
function GetParameterAttributesResponse(xml: Element): CpeGetResponse {
|
||||||
xml: Element
|
|
||||||
): GetParameterAttributesResponse {
|
|
||||||
return {
|
return {
|
||||||
name: "GetParameterAttributesResponse",
|
name: "GetParameterAttributesResponse",
|
||||||
parameterList: parameterAttributeList(
|
parameterList: parameterAttributeList(
|
||||||
xml.children.find((n) => n.localName === "ParameterList")
|
xml.children.find(n => n.localName === "ParameterList")
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterValues(methodRequest): string {
|
function SetParameterValues(methodRequest): string {
|
||||||
const params = methodRequest.parameterList.map((p) => {
|
const params = methodRequest.parameterList.map(p => {
|
||||||
let val = p[1];
|
let val = p[1];
|
||||||
if (p[2] === "xsd:dateTime" && typeof val === "number") {
|
if (p[2] === "xsd:dateTime" && typeof val === "number") {
|
||||||
val = new Date(val).toISOString();
|
val = new Date(val).toISOString();
|
||||||
@@ -314,35 +293,21 @@ function SetParameterValues(methodRequest): string {
|
|||||||
|
|
||||||
return `<cwmp:SetParameterValues><ParameterList soap-enc:arrayType="cwmp:ParameterValueStruct[${
|
return `<cwmp:SetParameterValues><ParameterList soap-enc:arrayType="cwmp:ParameterValueStruct[${
|
||||||
methodRequest.parameterList.length
|
methodRequest.parameterList.length
|
||||||
}]">${params.join("")}</ParameterList><ParameterKey>${
|
}]">${params.join(
|
||||||
methodRequest.parameterKey || ""
|
""
|
||||||
}</ParameterKey></cwmp:SetParameterValues>`;
|
)}</ParameterList><ParameterKey>${methodRequest.parameterKey ||
|
||||||
|
""}</ParameterKey></cwmp:SetParameterValues>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterValuesResponse(xml: Element): SetParameterValuesResponse {
|
function SetParameterValuesResponse(xml: Element): CpeSetResponse {
|
||||||
let status: number;
|
|
||||||
|
|
||||||
for (const c of xml.children) {
|
|
||||||
switch (c.localName) {
|
|
||||||
case "Status":
|
|
||||||
status = parseInt(c.text);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(status >= 0)) {
|
|
||||||
warnings.push({ message: "Invalid SetParameterValuesResponse status" });
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "SetParameterValuesResponse",
|
name: "SetParameterValuesResponse",
|
||||||
status: status,
|
status: parseInt(xml.children.find(n => n.localName === "Status").text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterAttributes(methodRequest): string {
|
function SetParameterAttributes(methodRequest): string {
|
||||||
const params = methodRequest.parameterList.map((p) => {
|
const params = methodRequest.parameterList.map(p => {
|
||||||
return `<SetParameterAttributesStruct><Name>${
|
return `<SetParameterAttributesStruct><Name>${
|
||||||
p[0]
|
p[0]
|
||||||
}</Name><NotificationChange>${
|
}</Name><NotificationChange>${
|
||||||
@@ -356,7 +321,7 @@ function SetParameterAttributes(methodRequest): string {
|
|||||||
}]">${
|
}]">${
|
||||||
p[2] == null
|
p[2] == null
|
||||||
? ""
|
? ""
|
||||||
: p[2].map((s) => `<string>${encodeEntities(s)}</string>`).join("")
|
: p[2].map(s => `<string>${encodeEntities(s)}</string>`).join("")
|
||||||
}</AccessList></SetParameterAttributesStruct>`;
|
}</AccessList></SetParameterAttributesStruct>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -365,26 +330,25 @@ function SetParameterAttributes(methodRequest): string {
|
|||||||
}]">${params.join("")}</ParameterList></cwmp:SetParameterAttributes>`;
|
}]">${params.join("")}</ParameterList></cwmp:SetParameterAttributes>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetParameterAttributesResponse(): SetParameterAttributesResponse {
|
function SetParameterAttributesResponse(): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "SetParameterAttributesResponse",
|
name: "SetParameterAttributesResponse"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddObject(methodRequest): string {
|
function AddObject(methodRequest): string {
|
||||||
return `<cwmp:AddObject><ObjectName>${
|
return `<cwmp:AddObject><ObjectName>${
|
||||||
methodRequest.objectName
|
methodRequest.objectName
|
||||||
}</ObjectName><ParameterKey>${
|
}</ObjectName><ParameterKey>${methodRequest.parameterKey ||
|
||||||
methodRequest.parameterKey || ""
|
""}</ParameterKey></cwmp:AddObject>`;
|
||||||
}</ParameterKey></cwmp:AddObject>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddObjectResponse(xml: Element): AddObjectResponse {
|
function AddObjectResponse(xml: Element): CpeSetResponse {
|
||||||
let instanceNumber: string, status: number;
|
let instanceNumber, status;
|
||||||
for (const c of xml.children) {
|
for (const c of xml.children) {
|
||||||
switch (c.localName) {
|
switch (c.localName) {
|
||||||
case "InstanceNumber":
|
case "InstanceNumber":
|
||||||
instanceNumber = c.text;
|
instanceNumber = parseInt(c.text);
|
||||||
break;
|
break;
|
||||||
case "Status":
|
case "Status":
|
||||||
status = parseInt(c.text);
|
status = parseInt(c.text);
|
||||||
@@ -392,57 +356,35 @@ function AddObjectResponse(xml: Element): AddObjectResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(status >= 0)) {
|
|
||||||
warnings.push({ message: "Invalid AddObjectResponse status" });
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "AddObjectResponse",
|
name: "AddObjectResponse",
|
||||||
instanceNumber: instanceNumber,
|
instanceNumber: instanceNumber,
|
||||||
status: status,
|
status: status
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteObject(methodRequest): string {
|
function DeleteObject(methodRequest): string {
|
||||||
return `<cwmp:DeleteObject><ObjectName>${
|
return `<cwmp:DeleteObject><ObjectName>${
|
||||||
methodRequest.objectName
|
methodRequest.objectName
|
||||||
}</ObjectName><ParameterKey>${
|
}</ObjectName><ParameterKey>${methodRequest.parameterKey ||
|
||||||
methodRequest.parameterKey || ""
|
""}</ParameterKey></cwmp:DeleteObject>`;
|
||||||
}</ParameterKey></cwmp:DeleteObject>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteObjectResponse(xml: Element): DeleteObjectResponse {
|
function DeleteObjectResponse(xml: Element): CpeSetResponse {
|
||||||
let status: number;
|
|
||||||
|
|
||||||
for (const c of xml.children) {
|
|
||||||
switch (c.localName) {
|
|
||||||
case "Status":
|
|
||||||
status = parseInt(c.text);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(status >= 0)) {
|
|
||||||
warnings.push({ message: "Invalid DeleteObjectResponse status" });
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "DeleteObjectResponse",
|
name: "DeleteObjectResponse",
|
||||||
status: status,
|
status: parseInt(xml.children.find(n => n.localName === "Status").text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Reboot(methodRequest): string {
|
function Reboot(methodRequest): string {
|
||||||
return `<cwmp:Reboot><CommandKey>${
|
return `<cwmp:Reboot><CommandKey>${methodRequest.commandKey ||
|
||||||
methodRequest.commandKey || ""
|
""}</CommandKey></cwmp:Reboot>`;
|
||||||
}</CommandKey></cwmp:Reboot>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RebootResponse(): RebootResponse {
|
function RebootResponse(): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "RebootResponse",
|
name: "RebootResponse"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,36 +392,33 @@ function FactoryReset(): string {
|
|||||||
return "<cwmp:FactoryReset></cwmp:FactoryReset>";
|
return "<cwmp:FactoryReset></cwmp:FactoryReset>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function FactoryResetResponse(): FactoryResetResponse {
|
function FactoryResetResponse(): CpeSetResponse {
|
||||||
return {
|
return {
|
||||||
name: "FactoryResetResponse",
|
name: "FactoryResetResponse"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Download(methodRequest): string {
|
function Download(methodRequest): string {
|
||||||
return `<cwmp:Download><CommandKey>${
|
return `<cwmp:Download><CommandKey>${methodRequest.commandKey ||
|
||||||
methodRequest.commandKey || ""
|
""}</CommandKey><FileType>${methodRequest.fileType}</FileType><URL>${
|
||||||
}</CommandKey><FileType>${methodRequest.fileType}</FileType><URL>${
|
|
||||||
methodRequest.url
|
methodRequest.url
|
||||||
}</URL><Username>${encodeEntities(
|
}</URL><Username>${encodeEntities(
|
||||||
methodRequest.username || ""
|
methodRequest.username || ""
|
||||||
)}</Username><Password>${encodeEntities(
|
)}</Username><Password>${encodeEntities(
|
||||||
methodRequest.password || ""
|
methodRequest.password || ""
|
||||||
)}</Password><FileSize>${
|
)}</Password><FileSize>${methodRequest.fileSize ||
|
||||||
methodRequest.fileSize || "0"
|
"0"}</FileSize><TargetFileName>${encodeEntities(
|
||||||
}</FileSize><TargetFileName>${encodeEntities(
|
|
||||||
methodRequest.targetFileName || ""
|
methodRequest.targetFileName || ""
|
||||||
)}</TargetFileName><DelaySeconds>${
|
)}</TargetFileName><DelaySeconds>${methodRequest.delaySeconds ||
|
||||||
methodRequest.delaySeconds || "0"
|
"0"}</DelaySeconds><SuccessURL>${encodeEntities(
|
||||||
}</DelaySeconds><SuccessURL>${encodeEntities(
|
|
||||||
methodRequest.successUrl || ""
|
methodRequest.successUrl || ""
|
||||||
)}</SuccessURL><FailureURL>${encodeEntities(
|
)}</SuccessURL><FailureURL>${encodeEntities(
|
||||||
methodRequest.failureUrl || ""
|
methodRequest.failureUrl || ""
|
||||||
)}</FailureURL></cwmp:Download>`;
|
)}</FailureURL></cwmp:Download>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadResponse(xml: Element): DownloadResponse {
|
function DownloadResponse(xml: Element): CpeSetResponse {
|
||||||
let status: number, startTime: number, completeTime: number;
|
let status, startTime, completeTime;
|
||||||
for (const c of xml.children) {
|
for (const c of xml.children) {
|
||||||
switch (c.localName) {
|
switch (c.localName) {
|
||||||
case "Status":
|
case "Status":
|
||||||
@@ -494,27 +433,21 @@ function DownloadResponse(xml: Element): DownloadResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(status >= 0)) {
|
|
||||||
warnings.push({ message: "Invalid DownloadResponse status" });
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "DownloadResponse",
|
name: "DownloadResponse",
|
||||||
status: status,
|
status: status,
|
||||||
startTime: startTime,
|
startTime: startTime,
|
||||||
completeTime: completeTime,
|
completeTime: completeTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inform(xml: Element): InformRequest {
|
function Inform(xml: Element): InformRequest {
|
||||||
let retryCount: number, evnt: string[];
|
let retryCount, evnt, parameterList;
|
||||||
let parameterList: [Path, string | number | boolean, string][];
|
|
||||||
const deviceId = {
|
const deviceId = {
|
||||||
Manufacturer: null,
|
Manufacturer: null,
|
||||||
OUI: null,
|
OUI: null,
|
||||||
ProductClass: null,
|
ProductClass: null,
|
||||||
SerialNumber: null,
|
SerialNumber: null
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const c of xml.children) {
|
for (const c of xml.children) {
|
||||||
@@ -542,7 +475,7 @@ function Inform(xml: Element): InformRequest {
|
|||||||
parameterList: parameterList,
|
parameterList: parameterList,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
event: evnt,
|
event: evnt,
|
||||||
retryCount: retryCount,
|
retryCount: retryCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +483,7 @@ function InformResponse(): string {
|
|||||||
return "<cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse>";
|
return "<cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetRPCMethods(): GetRPCMethodsRequest {
|
function GetRPCMethods(): AcsResponse {
|
||||||
return { name: "GetRPCMethods" };
|
return { name: "GetRPCMethods" };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +491,7 @@ function GetRPCMethodsResponse(methodResponse): string {
|
|||||||
return `<cwmp:GetRPCMethodsResponse><MethodList soap-enc:arrayType="xsd:string[${
|
return `<cwmp:GetRPCMethodsResponse><MethodList soap-enc:arrayType="xsd:string[${
|
||||||
methodResponse.methodList.length
|
methodResponse.methodList.length
|
||||||
}]">${methodResponse.methodList
|
}]">${methodResponse.methodList
|
||||||
.map((m) => `<string>${m}</string>`)
|
.map(m => `<string>${m}</string>`)
|
||||||
.join("")}</MethodList></cwmp:GetRPCMethodsResponse>`;
|
.join("")}</MethodList></cwmp:GetRPCMethodsResponse>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +519,7 @@ function TransferComplete(xml: Element): TransferCompleteRequest {
|
|||||||
commandKey: commandKey,
|
commandKey: commandKey,
|
||||||
faultStruct: _faultStruct,
|
faultStruct: _faultStruct,
|
||||||
startTime: startTime,
|
startTime: startTime,
|
||||||
completeTime: completeTime,
|
completeTime: completeTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,10 +527,10 @@ function TransferCompleteResponse(): string {
|
|||||||
return "<cwmp:TransferCompleteResponse></cwmp:TransferCompleteResponse>";
|
return "<cwmp:TransferCompleteResponse></cwmp:TransferCompleteResponse>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequestDownload(xml: Element): RequestDownloadRequest {
|
function RequestDownload(xml: Element): CpeRequest {
|
||||||
return {
|
return {
|
||||||
name: "RequestDownload",
|
name: "RequestDownload",
|
||||||
fileType: xml.children.find((n) => n.localName === "FileType").text,
|
fileType: xml.children.find(n => n.localName === "FileType").text
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,7 +566,7 @@ function faultStruct(xml: Element): FaultStruct {
|
|||||||
setParameterValuesFault.push({
|
setParameterValuesFault.push({
|
||||||
parameterName: pn,
|
parameterName: pn,
|
||||||
faultCode: fc,
|
faultCode: fc,
|
||||||
faultString: fs,
|
faultString: fs
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,7 +585,7 @@ function fault(xml: Element): CpeFault {
|
|||||||
faultString = decodeEntities(c.text);
|
faultString = decodeEntities(c.text);
|
||||||
break;
|
break;
|
||||||
case "detail":
|
case "detail":
|
||||||
detail = faultStruct(c.children.find((n) => n.localName === "Fault"));
|
detail = faultStruct(c.children.find(n => n.localName === "Fault"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,11 +593,7 @@ function fault(xml: Element): CpeFault {
|
|||||||
return { faultCode, faultString, detail };
|
return { faultCode, faultString, detail };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(
|
export function request(body: string, cwmpVersion, warn): SoapMessage {
|
||||||
body: string,
|
|
||||||
cwmpVersion: string,
|
|
||||||
warn: Record<string, unknown>[]
|
|
||||||
): SoapMessage {
|
|
||||||
warnings = warn;
|
warnings = warn;
|
||||||
|
|
||||||
const rpc = {
|
const rpc = {
|
||||||
@@ -673,7 +602,7 @@ export function request(
|
|||||||
sessionTimeout: null,
|
sessionTimeout: null,
|
||||||
cpeRequest: null,
|
cpeRequest: null,
|
||||||
cpeFault: null,
|
cpeFault: null,
|
||||||
cpeResponse: null,
|
cpeResponse: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!body.length) return rpc;
|
if (!body.length) return rpc;
|
||||||
@@ -720,9 +649,9 @@ export function request(
|
|||||||
const attrs = memoizedParseAttrs(e.attrs);
|
const attrs = memoizedParseAttrs(e.attrs);
|
||||||
const attr = namespace
|
const attr = namespace
|
||||||
? attrs.find(
|
? attrs.find(
|
||||||
(s) => s.namespace === "xmlns" && s.localName === namespace
|
s => s.namespace === "xmlns" && s.localName === namespace
|
||||||
)
|
)
|
||||||
: attrs.find((s) => s.name === "xmlns");
|
: attrs.find(s => s.name === "xmlns");
|
||||||
|
|
||||||
if (attr) namespaceHref = attr.value;
|
if (attr) namespaceHref = attr.value;
|
||||||
}
|
}
|
||||||
@@ -816,18 +745,13 @@ const namespacesAttrs = {
|
|||||||
.join(" "),
|
.join(" "),
|
||||||
"1.4": Object.entries(NAMESPACES["1.4"])
|
"1.4": Object.entries(NAMESPACES["1.4"])
|
||||||
.map(([k, v]) => `xmlns:${k}="${v}"`)
|
.map(([k, v]) => `xmlns:${k}="${v}"`)
|
||||||
.join(" "),
|
.join(" ")
|
||||||
};
|
};
|
||||||
|
|
||||||
export function response(rpc: {
|
export function response(rpc): { code: number; headers: {}; data: string } {
|
||||||
id: string;
|
|
||||||
acsRequest?: AcsRequest;
|
|
||||||
acsResponse?: AcsResponse;
|
|
||||||
cwmpVersion?: string;
|
|
||||||
}): { code: number; headers: Record<string, string>; data: string } {
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Server: SERVER_NAME,
|
Server: SERVER_NAME,
|
||||||
SOAPServer: SERVER_NAME,
|
SOAPServer: SERVER_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!rpc) return { code: 204, headers: headers, data: "" };
|
if (!rpc) return { code: 204, headers: headers, data: "" };
|
||||||
@@ -848,11 +772,7 @@ export function response(rpc: {
|
|||||||
body = RequestDownloadResponse();
|
body = RequestDownloadResponse();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
|
||||||
`Unknown method response type ${
|
|
||||||
(rpc.acsResponse as AcsResponse).name
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (rpc.acsRequest) {
|
} else if (rpc.acsRequest) {
|
||||||
switch (rpc.acsRequest.name) {
|
switch (rpc.acsRequest.name) {
|
||||||
@@ -887,9 +807,7 @@ export function response(rpc: {
|
|||||||
body = Download(rpc.acsRequest);
|
body = Download(rpc.acsRequest);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(`Unknown method request ${rpc.acsRequest.name}`);
|
||||||
`Unknown method request ${(rpc.acsRequest as AcsRequest).name}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,6 +819,6 @@ export function response(rpc: {
|
|||||||
namespacesAttrs[rpc.cwmpVersion]
|
namespacesAttrs[rpc.cwmpVersion]
|
||||||
}><soap-env:Header><cwmp:ID soap-env:mustUnderstand="1">${
|
}><soap-env:Header><cwmp:ID soap-env:mustUnderstand="1">${
|
||||||
rpc.id
|
rpc.id
|
||||||
}</cwmp:ID></soap-env:Header><soap-env:Body>${body}</soap-env:Body></soap-env:Envelope>`,
|
}</cwmp:ID></soap-env:Header><soap-env:Body>${body}</soap-env:Body></soap-env:Envelope>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+68
-212
@@ -22,7 +22,6 @@ import PathSet from "./common/path-set";
|
|||||||
import VersionedMap from "./versioned-map";
|
import VersionedMap from "./versioned-map";
|
||||||
import InstanceSet from "./instance-set";
|
import InstanceSet from "./instance-set";
|
||||||
import { IncomingMessage, ServerResponse } from "http";
|
import { IncomingMessage, ServerResponse } from "http";
|
||||||
import { Script } from "vm";
|
|
||||||
|
|
||||||
export type Expression = string | number | boolean | null | any[];
|
export type Expression = string | number | boolean | null | any[];
|
||||||
|
|
||||||
@@ -150,6 +149,7 @@ export interface SessionContext {
|
|||||||
faults?: { [channel: string]: SessionFault };
|
faults?: { [channel: string]: SessionFault };
|
||||||
retries?: { [channel: string]: number };
|
retries?: { [channel: string]: number };
|
||||||
cacheSnapshot?: string;
|
cacheSnapshot?: string;
|
||||||
|
configContext?: { [name: string]: any };
|
||||||
httpResponse?: ServerResponse;
|
httpResponse?: ServerResponse;
|
||||||
httpRequest?: IncomingMessage;
|
httpRequest?: IncomingMessage;
|
||||||
faultsTouched?: { [channel: string]: boolean };
|
faultsTouched?: { [channel: string]: boolean };
|
||||||
@@ -157,7 +157,6 @@ export interface SessionContext {
|
|||||||
new?: boolean;
|
new?: boolean;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
state: number;
|
state: number;
|
||||||
authState: number;
|
|
||||||
tasks?: Task[];
|
tasks?: Task[];
|
||||||
operations?: { [commandKey: string]: Operation };
|
operations?: { [commandKey: string]: Operation };
|
||||||
cacheUntil?: number;
|
cacheUntil?: number;
|
||||||
@@ -170,7 +169,7 @@ export interface SessionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
_id?: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
parameterNames?: string[];
|
parameterNames?: string[];
|
||||||
parameterValues?: [string, string | number | boolean, string?][];
|
parameterValues?: [string, string | number | boolean, string?][];
|
||||||
@@ -196,84 +195,47 @@ export interface Operation {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AcsRequest =
|
export interface AcsRequest {
|
||||||
| GetParameterNames
|
name: string;
|
||||||
| GetParameterValues
|
next?: string;
|
||||||
| GetParameterAttributes
|
|
||||||
| SetParameterValues
|
|
||||||
| SetParameterAttributes
|
|
||||||
| AddObject
|
|
||||||
| DeleteObject
|
|
||||||
| FactoryReset
|
|
||||||
| Reboot
|
|
||||||
| Download;
|
|
||||||
|
|
||||||
export interface GetParameterNames {
|
|
||||||
name: "GetParameterNames";
|
|
||||||
parameterPath: string;
|
|
||||||
nextLevel: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetParameterValues {
|
export interface GetAcsRequest extends AcsRequest {
|
||||||
name: "GetParameterValues";
|
name: "GetParameterNames" | "GetParameterValues" | "GetParameterAttributes";
|
||||||
parameterNames: string[];
|
objectName?: string;
|
||||||
|
parameterNames?: string[];
|
||||||
|
parameterPath?: string;
|
||||||
|
nextLevel?: boolean;
|
||||||
|
instanceValues?: { [name: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetParameterAttributes {
|
export interface SetAcsRequest extends AcsRequest {
|
||||||
name: "GetParameterAttributes";
|
name:
|
||||||
parameterNames: string[];
|
| "SetParameterValues"
|
||||||
}
|
| "SetParameterAttributes"
|
||||||
|
| "AddObject"
|
||||||
export interface SetParameterValues {
|
| "DeleteObject"
|
||||||
name: "SetParameterValues";
|
| "FactoryReset"
|
||||||
parameterList: [string, boolean | number | string, string][];
|
| "Reboot"
|
||||||
parameterKey?: string;
|
| "Download";
|
||||||
|
parameterList?:
|
||||||
|
| [string, string | number | boolean, string][]
|
||||||
|
| [string, number, string[]][];
|
||||||
|
instanceValues?: { [param: string]: string | number | boolean };
|
||||||
|
objectName?: string;
|
||||||
DATETIME_MILLISECONDS?: boolean;
|
DATETIME_MILLISECONDS?: boolean;
|
||||||
BOOLEAN_LITERAL?: boolean;
|
BOOLEAN_LITERAL?: boolean;
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetParameterAttributes {
|
|
||||||
name: "SetParameterAttributes";
|
|
||||||
parameterList: [string, number, string[]][];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddObject {
|
|
||||||
name: "AddObject";
|
|
||||||
objectName: string;
|
|
||||||
parameterKey?: string;
|
|
||||||
instanceValues: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteObject {
|
|
||||||
name: "DeleteObject";
|
|
||||||
objectName: string;
|
|
||||||
parameterKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FactoryReset {
|
|
||||||
name: "FactoryReset";
|
|
||||||
commandKey?: string;
|
commandKey?: string;
|
||||||
}
|
instance?: string;
|
||||||
|
fileType?: string;
|
||||||
export interface Reboot {
|
|
||||||
name: "Reboot";
|
|
||||||
commandKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Download {
|
|
||||||
name: "Download";
|
|
||||||
commandKey: string;
|
|
||||||
instance: string;
|
|
||||||
fileType: string;
|
|
||||||
fileName?: string;
|
|
||||||
url?: string;
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
fileSize?: number;
|
fileSize?: number;
|
||||||
|
url?: string;
|
||||||
|
fileName?: string;
|
||||||
targetFileName?: string;
|
targetFileName?: string;
|
||||||
delaySecods?: number;
|
}
|
||||||
successUrl?: string;
|
|
||||||
failureUrl?: string;
|
export interface CpeResponse {
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpvFault {
|
export interface SpvFault {
|
||||||
@@ -294,75 +256,38 @@ export interface CpeFault {
|
|||||||
detail?: FaultStruct;
|
detail?: FaultStruct;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CpeResponse =
|
export interface CpeGetResponse extends CpeResponse {
|
||||||
| GetParameterNamesResponse
|
name:
|
||||||
| GetParameterValuesResponse
|
| "GetParameterNamesResponse"
|
||||||
| GetParameterAttributesResponse
|
| "GetParameterValuesResponse"
|
||||||
| SetParameterValuesResponse
|
| "GetParameterAttributesResponse";
|
||||||
| SetParameterAttributesResponse
|
parameterList?:
|
||||||
| AddObjectResponse
|
| [string, boolean][]
|
||||||
| DeleteObjectResponse
|
| [string, string | number | boolean, string][]
|
||||||
| RebootResponse
|
| [string, number, string[]][];
|
||||||
| FactoryResetResponse
|
|
||||||
| DownloadResponse;
|
|
||||||
|
|
||||||
export interface GetParameterNamesResponse {
|
|
||||||
name: "GetParameterNamesResponse";
|
|
||||||
parameterList: [Path, boolean, boolean][];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetParameterValuesResponse {
|
export interface CpeSetResponse extends CpeResponse {
|
||||||
name: "GetParameterValuesResponse";
|
name:
|
||||||
parameterList: [Path, string | number | boolean, string][];
|
| "SetParameterValuesResponse"
|
||||||
}
|
| "SetParameterAttributesResponse"
|
||||||
|
| "AddObjectResponse"
|
||||||
export interface GetParameterAttributesResponse {
|
| "DeleteObjectResponse"
|
||||||
name: "GetParameterAttributesResponse";
|
| "RebootResponse"
|
||||||
parameterList: [Path, number, string[]][];
|
| "FactoryResetResponse"
|
||||||
}
|
| "DownloadResponse";
|
||||||
|
status?: number;
|
||||||
export interface SetParameterValuesResponse {
|
instanceNumber?: string;
|
||||||
name: "SetParameterValuesResponse";
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetParameterAttributesResponse {
|
|
||||||
name: "SetParameterAttributesResponse";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddObjectResponse {
|
|
||||||
name: "AddObjectResponse";
|
|
||||||
instanceNumber: string;
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteObjectResponse {
|
|
||||||
name: "DeleteObjectResponse";
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RebootResponse {
|
|
||||||
name: "RebootResponse";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FactoryResetResponse {
|
|
||||||
name: "FactoryResetResponse";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadResponse {
|
|
||||||
name: "DownloadResponse";
|
|
||||||
status: number;
|
|
||||||
startTime?: number;
|
startTime?: number;
|
||||||
completeTime?: number;
|
completeTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CpeRequest =
|
export interface CpeRequest {
|
||||||
| InformRequest
|
name: string;
|
||||||
| TransferCompleteRequest
|
fileType?: string;
|
||||||
| GetRPCMethodsRequest
|
}
|
||||||
| RequestDownloadRequest;
|
|
||||||
|
|
||||||
export interface InformRequest {
|
export interface InformRequest extends CpeRequest {
|
||||||
name: "Inform";
|
name: "Inform";
|
||||||
deviceId: {
|
deviceId: {
|
||||||
Manufacturer: string;
|
Manufacturer: string;
|
||||||
@@ -372,10 +297,10 @@ export interface InformRequest {
|
|||||||
};
|
};
|
||||||
event: string[];
|
event: string[];
|
||||||
retryCount: number;
|
retryCount: number;
|
||||||
parameterList: [Path, string | number | boolean, string][];
|
parameterList: [string, string | number | boolean, string][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransferCompleteRequest {
|
export interface TransferCompleteRequest extends CpeRequest {
|
||||||
name: "TransferComplete";
|
name: "TransferComplete";
|
||||||
commandKey?: string;
|
commandKey?: string;
|
||||||
faultStruct?: FaultStruct;
|
faultStruct?: FaultStruct;
|
||||||
@@ -383,36 +308,10 @@ export interface TransferCompleteRequest {
|
|||||||
completeTime?: number;
|
completeTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetRPCMethodsRequest {
|
export interface AcsResponse {
|
||||||
name: "GetRPCMethods";
|
name: string;
|
||||||
}
|
commandKey?: string;
|
||||||
|
faultStruct?: FaultStruct;
|
||||||
export interface RequestDownloadRequest {
|
|
||||||
name: "RequestDownload";
|
|
||||||
fileType: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AcsResponse =
|
|
||||||
| InformResponse
|
|
||||||
| GetRPCMethodsResponse
|
|
||||||
| TransferCompleteResponse
|
|
||||||
| RequestDownloadResponse;
|
|
||||||
|
|
||||||
export interface InformResponse {
|
|
||||||
name: "InformResponse";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetRPCMethodsResponse {
|
|
||||||
name: "GetRPCMethodsResponse";
|
|
||||||
methodList: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TransferCompleteResponse {
|
|
||||||
name: "TransferCompleteResponse";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestDownloadResponse {
|
|
||||||
name: "RequestDownloadResponse";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryOptions {
|
export interface QueryOptions {
|
||||||
@@ -427,7 +326,7 @@ export interface QueryOptions {
|
|||||||
export interface Declaration {
|
export interface Declaration {
|
||||||
path: Path;
|
path: Path;
|
||||||
pathGet: number;
|
pathGet: number;
|
||||||
pathSet?: number | [number, number];
|
pathSet?: [number, number];
|
||||||
attrGet?: {
|
attrGet?: {
|
||||||
object?: number;
|
object?: number;
|
||||||
writable?: number;
|
writable?: number;
|
||||||
@@ -463,36 +362,8 @@ export interface Preset {
|
|||||||
channel: string;
|
channel: string;
|
||||||
schedule?: { md5: string; duration: number; schedule: any };
|
schedule?: { md5: string; duration: number; schedule: any };
|
||||||
events?: { [event: string]: boolean };
|
events?: { [event: string]: boolean };
|
||||||
precondition?: Expression;
|
precondition?: {};
|
||||||
provisions: [string, ...Expression[]][];
|
provisions: string[][];
|
||||||
}
|
|
||||||
|
|
||||||
export interface Provisions {
|
|
||||||
[name: string]: { md5: string; script: Script };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VirtualParameters {
|
|
||||||
[name: string]: { md5: string; script: Script };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Files {
|
|
||||||
[name: string]: { length: number; md5: string; contentType: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Users {
|
|
||||||
[name: string]: { password: string; salt: string; roles: string[] };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Permissions {
|
|
||||||
[role: string]: {
|
|
||||||
[access: number]: {
|
|
||||||
[resource: string]: {
|
|
||||||
access: number;
|
|
||||||
filter: Expression;
|
|
||||||
validate?: Expression;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PermissionSet = {
|
export type PermissionSet = {
|
||||||
@@ -503,21 +374,6 @@ export type PermissionSet = {
|
|||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
[name: string]: Expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UiConfig {
|
|
||||||
filters: Record<string, unknown>;
|
|
||||||
device: Record<string, unknown>;
|
|
||||||
index: Record<string, unknown>;
|
|
||||||
overview: {
|
|
||||||
charts?: Record<string, unknown>;
|
|
||||||
groups?: Record<string, unknown>;
|
|
||||||
};
|
|
||||||
pageSize?: Expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SoapMessage {
|
export interface SoapMessage {
|
||||||
id: string;
|
id: string;
|
||||||
cwmpVersion: string;
|
cwmpVersion: string;
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import { PermissionSet } from "./types";
|
|||||||
import { authLocal } from "./ui/api-functions";
|
import { authLocal } from "./ui/api-functions";
|
||||||
import * as init from "./init";
|
import * as init from "./init";
|
||||||
import { version as VERSION } from "../package.json";
|
import { version as VERSION } from "../package.json";
|
||||||
import memoize from "./common/memoize";
|
|
||||||
|
|
||||||
declare module "koa" {
|
declare module "koa" {
|
||||||
interface Request {
|
interface Request {
|
||||||
@@ -48,18 +47,7 @@ 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";
|
||||||
|
|
||||||
const getAuthorizer = memoize(
|
koa.on("error", async err => {
|
||||||
(snapshot: string, rolesStr: string): Authorizer => {
|
|
||||||
const roles: string[] = JSON.parse(rolesStr);
|
|
||||||
const allPermissions = localCache.getPermissions(snapshot);
|
|
||||||
const permissionSets: PermissionSet[] = roles.map((r) =>
|
|
||||||
Object.values(allPermissions[r] || {})
|
|
||||||
);
|
|
||||||
return new Authorizer(permissionSets);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
koa.on("error", async (err) => {
|
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,14 +72,16 @@ koa.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
koa.use(async (ctx, next) => {
|
koa.use(async (ctx, next) => {
|
||||||
let roles: string[] = [];
|
const permissionSets: PermissionSet[] = [];
|
||||||
|
|
||||||
if (ctx.state.user && ctx.state.user.username) {
|
if (ctx.state.user && ctx.state.user.username) {
|
||||||
|
const allPermissions = localCache.getPermissions(ctx.state.configSnapshot);
|
||||||
|
|
||||||
let user;
|
let user;
|
||||||
if (ctx.state.user.authMethod === "local") {
|
if (ctx.state.user.authMethod === "local") {
|
||||||
user = localCache.getUsers(ctx.state.configSnapshot)[
|
user = localCache.getUsers(ctx.state.configSnapshot)[
|
||||||
@@ -100,18 +90,17 @@ koa.use(async (ctx, next) => {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error("Invalid auth method");
|
throw new Error("Invalid auth method");
|
||||||
}
|
}
|
||||||
roles = user.roles || [];
|
|
||||||
|
for (const role of (user.roles || []).sort())
|
||||||
|
permissionSets.push(Object.values(allPermissions[role] || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.state.authorizer = getAuthorizer(
|
ctx.state.authorizer = new Authorizer(permissionSets);
|
||||||
ctx.state.configSnapshot,
|
|
||||||
JSON.stringify(roles)
|
|
||||||
);
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/login", async (ctx) => {
|
router.post("/login", async ctx => {
|
||||||
if (!JWT_SECRET) {
|
if (!JWT_SECRET) {
|
||||||
ctx.status = 500;
|
ctx.status = 500;
|
||||||
ctx.body = "UI_JWT_SECRET is not set";
|
ctx.body = "UI_JWT_SECRET is not set";
|
||||||
@@ -126,7 +115,7 @@ router.post("/login", async (ctx) => {
|
|||||||
message: "Log in",
|
message: "Log in",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
username: username,
|
username: username,
|
||||||
method: null,
|
method: null
|
||||||
};
|
};
|
||||||
|
|
||||||
function success(authMethod): void {
|
function success(authMethod): void {
|
||||||
@@ -150,13 +139,13 @@ router.post("/login", async (ctx) => {
|
|||||||
failure();
|
failure();
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/logout", async (ctx) => {
|
router.post("/logout", async ctx => {
|
||||||
ctx.cookies.set(JWT_COOKIE); // Delete cookie
|
ctx.cookies.set(JWT_COOKIE); // Delete cookie
|
||||||
ctx.body = "";
|
ctx.body = "";
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
message: "Log out",
|
message: "Log out",
|
||||||
context: ctx,
|
context: ctx
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,11 +159,11 @@ koa.use(async (ctx, next) => {
|
|||||||
koa.use(koaBodyParser());
|
koa.use(koaBodyParser());
|
||||||
router.use("/api", api.routes(), api.allowedMethods());
|
router.use("/api", api.routes(), api.allowedMethods());
|
||||||
|
|
||||||
router.get("/status", (ctx) => {
|
router.get("/status", ctx => {
|
||||||
ctx.body = "OK";
|
ctx.body = "OK";
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/init", async (ctx) => {
|
router.get("/init", async ctx => {
|
||||||
const status = await init.getStatus();
|
const status = await init.getStatus();
|
||||||
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
|
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
|
||||||
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
|
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
|
||||||
@@ -195,7 +184,7 @@ router.get("/init", async (ctx) => {
|
|||||||
ctx.body = status;
|
ctx.body = status;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/init", async (ctx) => {
|
router.post("/init", async ctx => {
|
||||||
const status = ctx.request.body;
|
const status = ctx.request.body;
|
||||||
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
|
if (Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length) {
|
||||||
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
|
if (!ctx.state.authorizer.hasAccess("users", 3)) status["users"] = false;
|
||||||
@@ -216,8 +205,8 @@ router.post("/init", async (ctx) => {
|
|||||||
ctx.body = "";
|
ctx.body = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/", async (ctx) => {
|
router.get("/", async ctx => {
|
||||||
const permissionSets: PermissionSet[] = ctx.state.authorizer.getPermissionSets();
|
const permissionSets = ctx.state.authorizer.getPermissionSets();
|
||||||
|
|
||||||
let wizard = "";
|
let wizard = "";
|
||||||
if (!Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length)
|
if (!Object.keys(localCache.getUsers(ctx.state.configSnapshot)).length)
|
||||||
@@ -234,7 +223,7 @@ router.get("/", async (ctx) => {
|
|||||||
<noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript>
|
<noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript>
|
||||||
<script>
|
<script>
|
||||||
window.clientConfig = ${JSON.stringify({
|
window.clientConfig = ${JSON.stringify({
|
||||||
ui: localCache.getUiConfig(ctx.state.configSnapshot),
|
ui: localCache.getUiConfig(ctx.state.configSnapshot)
|
||||||
})};
|
})};
|
||||||
window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)};
|
window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)};
|
||||||
window.genieacsVersion = ${JSON.stringify(VERSION)};
|
window.genieacsVersion = ${JSON.stringify(VERSION)};
|
||||||
@@ -251,12 +240,7 @@ router.get("/", async (ctx) => {
|
|||||||
|
|
||||||
koa.use(
|
koa.use(
|
||||||
koaCompress({
|
koaCompress({
|
||||||
gzip: {
|
flush: Z_SYNC_FLUSH
|
||||||
flush: Z_SYNC_FLUSH,
|
|
||||||
},
|
|
||||||
deflate: {
|
|
||||||
flush: Z_SYNC_FLUSH,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+17
-32
@@ -26,9 +26,8 @@ import {
|
|||||||
insertTasks,
|
insertTasks,
|
||||||
watchTask,
|
watchTask,
|
||||||
connectionRequest,
|
connectionRequest,
|
||||||
deleteDevice,
|
deleteDevice
|
||||||
} from "../api-functions";
|
} from "../api-functions";
|
||||||
import { Task } from "../types";
|
|
||||||
|
|
||||||
async function deleteFault(id): Promise<void> {
|
async function deleteFault(id): Promise<void> {
|
||||||
const deviceId = id.split(":", 1)[0];
|
const deviceId = id.split(":", 1)[0];
|
||||||
@@ -38,16 +37,13 @@ async function deleteFault(id): Promise<void> {
|
|||||||
db.deleteFault(id),
|
db.deleteFault(id),
|
||||||
channel.startsWith("task_")
|
channel.startsWith("task_")
|
||||||
? db.deleteTask(new ObjectID(channel.slice(5)))
|
? db.deleteTask(new ObjectID(channel.slice(5)))
|
||||||
: null,
|
: null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await del(`${deviceId}_tasks_faults_operations`);
|
await del(`${deviceId}_tasks_faults_operations`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteResource(
|
export async function deleteResource(resource, id): Promise<void> {
|
||||||
resource: string,
|
|
||||||
id: string
|
|
||||||
): Promise<void> {
|
|
||||||
switch (resource) {
|
switch (resource) {
|
||||||
case "devices":
|
case "devices":
|
||||||
await deleteDevice(id);
|
await deleteDevice(id);
|
||||||
@@ -90,30 +86,28 @@ export async function deleteResource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function postTasks(
|
export async function postTasks(
|
||||||
deviceId: string,
|
deviceId,
|
||||||
tasks: Task[],
|
tasks,
|
||||||
timeout: number,
|
timeout
|
||||||
device: Record<string, { value?: [boolean | number | string, string] }>
|
|
||||||
): Promise<{ connectionRequest: string; tasks: any[] }> {
|
): Promise<{ connectionRequest: string; tasks: any[] }> {
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
delete task._id;
|
delete task._id;
|
||||||
task["device"] = deviceId;
|
task.device = deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks = await insertTasks(tasks);
|
tasks = await insertTasks(tasks);
|
||||||
const statuses = tasks.map((t) => {
|
const statuses = tasks.map(t => {
|
||||||
return { _id: t._id, status: "pending" };
|
return { _id: t._id, status: "pending" };
|
||||||
});
|
});
|
||||||
|
|
||||||
await del(`${deviceId}_tasks_faults_operations`);
|
await del(`${deviceId}_tasks_faults_operations`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await connectionRequest(deviceId, device);
|
await connectionRequest(deviceId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await Promise.all(statuses.map((t) => db.deleteTask(new ObjectID(t._id))));
|
|
||||||
return {
|
return {
|
||||||
connectionRequest: err.message,
|
connectionRequest: err.message,
|
||||||
tasks: statuses,
|
tasks: statuses
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,12 +130,11 @@ export async function postTasks(
|
|||||||
r.status = "done";
|
r.status = "done";
|
||||||
} else if (res[i * 2 + 1].length === 1) {
|
} else if (res[i * 2 + 1].length === 1) {
|
||||||
r.status = "fault";
|
r.status = "fault";
|
||||||
r["fault"] = res[i * 2 + 1][0];
|
r.fault = res[i * 2 + 1][0];
|
||||||
}
|
}
|
||||||
|
db.deleteTask(r._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(statuses.map((t) => db.deleteTask(new ObjectID(t._id))));
|
|
||||||
|
|
||||||
return { connectionRequest: "OK", tasks: statuses };
|
return { connectionRequest: "OK", tasks: statuses };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,11 +148,7 @@ interface PingResponse {
|
|||||||
mdev: number;
|
mdev: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putResource(
|
export async function putResource(resource, id, data): Promise<void> {
|
||||||
resource: string,
|
|
||||||
id: string,
|
|
||||||
data: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
if (resource === "presets") {
|
if (resource === "presets") {
|
||||||
await db.putPreset(id, data);
|
await db.putPreset(id, data);
|
||||||
} else if (resource === "provisions") {
|
} else if (resource === "provisions") {
|
||||||
@@ -171,25 +160,21 @@ export async function putResource(
|
|||||||
} else if (resource === "permissions") {
|
} else if (resource === "permissions") {
|
||||||
await db.putPermission(id, data);
|
await db.putPermission(id, data);
|
||||||
} else if (resource === "users") {
|
} else if (resource === "users") {
|
||||||
delete data["password"];
|
delete data.password;
|
||||||
delete data["salt"];
|
delete data.salt;
|
||||||
await db.putUser(id, data);
|
await db.putUser(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
await del("presets_hash");
|
await del("presets_hash");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authLocal(
|
export function authLocal(snapshot, username, password): Promise<boolean> {
|
||||||
snapshot: string,
|
|
||||||
username: string,
|
|
||||||
password: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const users = getUsers(snapshot);
|
const users = getUsers(snapshot);
|
||||||
const user = users[username];
|
const user = users[username];
|
||||||
if (!user || !user.password) return void resolve(null);
|
if (!user || !user.password) return void resolve(null);
|
||||||
hashPassword(password, user.salt)
|
hashPassword(password, user.salt)
|
||||||
.then((hash) => {
|
.then(hash => {
|
||||||
if (hash === user.password) resolve(true);
|
if (hash === user.password) resolve(true);
|
||||||
else resolve(false);
|
else resolve(false);
|
||||||
})
|
})
|
||||||
|
|||||||
+45
-76
@@ -30,7 +30,6 @@ import { generateSalt, hashPassword } from "../auth";
|
|||||||
import { del } from "../cache";
|
import { del } from "../cache";
|
||||||
import Authorizer from "../common/authorizer";
|
import Authorizer from "../common/authorizer";
|
||||||
import { ping } from "../ping";
|
import { ping } from "../ping";
|
||||||
import * as url from "url";
|
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
export default router;
|
export default router;
|
||||||
@@ -53,7 +52,7 @@ const RESOURCE_IDS = {
|
|||||||
permissions: "_id",
|
permissions: "_id",
|
||||||
users: "_id",
|
users: "_id",
|
||||||
faults: "_id",
|
faults: "_id",
|
||||||
tasks: "_id",
|
tasks: "_id"
|
||||||
};
|
};
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
@@ -66,21 +65,21 @@ const resources = {
|
|||||||
permissions: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
permissions: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
||||||
users: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
users: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
||||||
faults: 0 | RESOURCE_DELETE,
|
faults: 0 | RESOURCE_DELETE,
|
||||||
tasks: 0,
|
tasks: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get(`/devices/:id.csv`, async (ctx) => {
|
router.get(`/devices/:id.csv`, async ctx => {
|
||||||
const authorizer: Authorizer = ctx.state.authorizer;
|
const authorizer: Authorizer = ctx.state.authorizer;
|
||||||
const log = {
|
const log = {
|
||||||
message: "Query device (CSV)",
|
message: "Query device (CSV)",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
id: ctx.params.id,
|
id: ctx.params.id
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter("devices", 2), [
|
const filter = and(authorizer.getFilter("devices", 2), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", RESOURCE_IDS.devices],
|
["PARAM", RESOURCE_IDS.devices],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!authorizer.hasAccess("devices", 2)) {
|
if (!authorizer.hasAccess("devices", 2)) {
|
||||||
@@ -117,9 +116,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
|
|||||||
p.notification,
|
p.notification,
|
||||||
p.notificationTimestamp,
|
p.notificationTimestamp,
|
||||||
p.accessList ? p.accessList.join(", ") : "",
|
p.accessList ? p.accessList.join(", ") : "",
|
||||||
p.accessListTimestamp,
|
p.accessListTimestamp
|
||||||
];
|
];
|
||||||
ctx.body.write(row.map((r) => (r != null ? r : "")).join(",") + "\n");
|
ctx.body.write(row.map(r => (r != null ? r : "")).join(",") + "\n");
|
||||||
}
|
}
|
||||||
ctx.body.end();
|
ctx.body.end();
|
||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
@@ -136,7 +135,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
message: `Count ${resource}`,
|
message: `Count ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
filter: ctx.request.query.filter,
|
filter: ctx.request.query.filter,
|
||||||
count: null,
|
count: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!authorizer.hasAccess(resource, 1)) {
|
if (!authorizer.hasAccess(resource, 1)) {
|
||||||
@@ -147,9 +146,8 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
// Exclude temporary tasks and faults
|
// Exclude temporary tasks and faults
|
||||||
if (resource === "tasks" || resource === "faults") {
|
if (resource === "tasks" || resource === "faults") {
|
||||||
filter = and(filter, [
|
filter = and(filter, [
|
||||||
"OR",
|
"NOT",
|
||||||
[">=", ["PARAM", "expiry"], Date.now() + 60000],
|
["<", ["PARAM", "expiry"], Date.now() + 60000]
|
||||||
["IS NULL", ["PARAM", "expiry"]],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +183,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
limit: options.limit,
|
limit: options.limit,
|
||||||
skip: options.skip,
|
skip: options.skip,
|
||||||
sort: options.sort,
|
sort: options.sort,
|
||||||
projection: options.projection,
|
projection: options.projection
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!authorizer.hasAccess(resource, 2)) {
|
if (!authorizer.hasAccess(resource, 2)) {
|
||||||
@@ -196,9 +194,8 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
// Exclude temporary tasks and faults
|
// Exclude temporary tasks and faults
|
||||||
if (resource === "tasks" || resource === "faults") {
|
if (resource === "tasks" || resource === "faults") {
|
||||||
filter = and(filter, [
|
filter = and(filter, [
|
||||||
"OR",
|
"NOT",
|
||||||
[">=", ["PARAM", "expiry"], Date.now() + 60000],
|
["<", ["PARAM", "expiry"], Date.now() + 60000]
|
||||||
["IS NULL", ["PARAM", "expiry"]],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +204,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
|
|
||||||
let c = 0;
|
let c = 0;
|
||||||
ctx.body.write("[\n");
|
ctx.body.write("[\n");
|
||||||
await db.query(resource, filter, options, (obj) => {
|
await db.query(resource, filter, options, obj => {
|
||||||
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
|
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
|
||||||
});
|
});
|
||||||
ctx.body.end("]");
|
ctx.body.end("]");
|
||||||
@@ -233,7 +230,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
filter: ctx.request.query.filter,
|
filter: ctx.request.query.filter,
|
||||||
limit: options.limit,
|
limit: options.limit,
|
||||||
skip: options.skip,
|
skip: options.skip,
|
||||||
sort: options.sort,
|
sort: options.sort
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!authorizer.hasAccess(resource, 2)) {
|
if (!authorizer.hasAccess(resource, 2)) {
|
||||||
@@ -241,9 +238,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
return void next();
|
return void next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: Record<string, Expression> = JSON.parse(
|
const columns = JSON.parse(ctx.request.query.columns);
|
||||||
ctx.request.query.columns
|
|
||||||
);
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(columns)) {
|
for (const [k, v] of Object.entries(columns)) {
|
||||||
@@ -256,9 +251,8 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
// Exclude temporary tasks and faults
|
// Exclude temporary tasks and faults
|
||||||
if (resource === "tasks" || resource === "faults") {
|
if (resource === "tasks" || resource === "faults") {
|
||||||
filter = and(filter, [
|
filter = and(filter, [
|
||||||
"OR",
|
"NOT",
|
||||||
[">=", ["PARAM", "expiry"], Date.now() + 60000],
|
["<", ["PARAM", "expiry"], Date.now() + 60000]
|
||||||
["IS NULL", ["PARAM", "expiry"]],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,11 +263,11 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ctx.body.write(
|
ctx.body.write(
|
||||||
Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n"
|
Object.keys(columns).map(k => `"${k.replace(/"/, '""')}"`) + "\n"
|
||||||
);
|
);
|
||||||
await db.query(resource, filter, options, (obj) => {
|
await db.query(resource, filter, options, obj => {
|
||||||
const arr = Object.values(columns).map((exp) => {
|
const arr = Object.values(columns).map(exp => {
|
||||||
const v = evaluate(exp, obj, null, (e) => {
|
const v = evaluate(exp, obj, null, e => {
|
||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
if (e[0] === "PARAM") {
|
if (e[0] === "PARAM") {
|
||||||
if (resource === "devices") {
|
if (resource === "devices") {
|
||||||
@@ -312,13 +306,13 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
const log = {
|
const log = {
|
||||||
message: `Count ${resource}`,
|
message: `Count ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`,
|
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter(resource, 2), [
|
const filter = and(authorizer.getFilter(resource, 2), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", RESOURCE_IDS[resource]],
|
["PARAM", RESOURCE_IDS[resource]],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
if (!authorizer.hasAccess(resource, 2)) {
|
if (!authorizer.hasAccess(resource, 2)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -338,13 +332,13 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
const log = {
|
const log = {
|
||||||
message: `Query ${resource}`,
|
message: `Query ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`,
|
filter: `${RESOURCE_IDS[resource]} = "${ctx.params.id}"`
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter(resource, 2), [
|
const filter = and(authorizer.getFilter(resource, 2), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", RESOURCE_IDS[resource]],
|
["PARAM", RESOURCE_IDS[resource]],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
if (!authorizer.hasAccess(resource, 2)) {
|
if (!authorizer.hasAccess(resource, 2)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -365,13 +359,13 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
const log = {
|
const log = {
|
||||||
message: `Delete ${resource}`,
|
message: `Delete ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
id: ctx.params.id,
|
id: ctx.params.id
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter(resource, 3), [
|
const filter = and(authorizer.getFilter(resource, 3), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", RESOURCE_IDS[resource]],
|
["PARAM", RESOURCE_IDS[resource]],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
if (!authorizer.hasAccess(resource, 3)) {
|
if (!authorizer.hasAccess(resource, 3)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -402,7 +396,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
const log = {
|
const log = {
|
||||||
message: `Put ${resource}`,
|
message: `Put ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
id: id,
|
id: id
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!authorizer.hasAccess(resource, 3)) {
|
if (!authorizer.hasAccess(resource, 3)) {
|
||||||
@@ -443,7 +437,7 @@ router.put("/files/:id", async (ctx, next) => {
|
|||||||
message: `Upload ${resource}`,
|
message: `Upload ${resource}`,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
id: id,
|
id: id,
|
||||||
metadata: null,
|
metadata: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!authorizer.hasAccess(resource, 3)) {
|
if (!authorizer.hasAccess(resource, 3)) {
|
||||||
@@ -455,7 +449,7 @@ router.put("/files/:id", async (ctx, next) => {
|
|||||||
fileType: ctx.request.headers["metadata.filetype"] || "",
|
fileType: ctx.request.headers["metadata.filetype"] || "",
|
||||||
oui: ctx.request.headers["metadata.oui"] || "",
|
oui: ctx.request.headers["metadata.oui"] || "",
|
||||||
productClass: ctx.request.headers["metadata.productclass"] || "",
|
productClass: ctx.request.headers["metadata.productclass"] || "",
|
||||||
version: ctx.request.headers["metadata.version"] || "",
|
version: ctx.request.headers["metadata.version"] || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const validate = authorizer.getValidator(resource, metadata);
|
const validate = authorizer.getValidator(resource, metadata);
|
||||||
@@ -464,12 +458,6 @@ router.put("/files/:id", async (ctx, next) => {
|
|||||||
return void (ctx.status = 403);
|
return void (ctx.status = 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await db.deleteFile(id);
|
|
||||||
} catch (err) {
|
|
||||||
// File doesn't exist, ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.putFile(id, metadata, ctx.req);
|
await db.putFile(id, metadata, ctx.req);
|
||||||
log.metadata = metadata;
|
log.metadata = metadata;
|
||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
@@ -483,13 +471,13 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
|
|||||||
message: "Commit tasks",
|
message: "Commit tasks",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
deviceId: ctx.params.id,
|
deviceId: ctx.params.id,
|
||||||
tasks: null,
|
tasks: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter("devices", 3), [
|
const filter = and(authorizer.getFilter("devices", 3), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", "DeviceID.ID"],
|
["PARAM", "DeviceID.ID"],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
if (!authorizer.hasAccess("devices", 3)) {
|
if (!authorizer.hasAccess("devices", 3)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -497,9 +485,8 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
|
|||||||
}
|
}
|
||||||
const devices = await db.query("devices", filter);
|
const devices = await db.query("devices", filter);
|
||||||
if (!devices.length) return void next();
|
if (!devices.length) return void next();
|
||||||
const device = devices[0];
|
|
||||||
|
|
||||||
const validate = authorizer.getValidator("devices", device);
|
const validate = authorizer.getValidator("devices", devices[0]);
|
||||||
for (const t of ctx.request.body) {
|
for (const t of ctx.request.body) {
|
||||||
if (!validate("task", t)) {
|
if (!validate("task", t)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -507,37 +494,19 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Pass context and timestamp
|
||||||
const onlineThreshold = getConfig(
|
const onlineThreshold = getConfig(
|
||||||
ctx.state.configSnapshot,
|
ctx.state.configSnapshot,
|
||||||
"cwmp.deviceOnlineThreshold",
|
"cwmp.deviceOnlineThreshold"
|
||||||
{},
|
);
|
||||||
Date.now(),
|
|
||||||
(exp) => {
|
|
||||||
if (!Array.isArray(exp)) return exp;
|
|
||||||
if (exp[0] === "PARAM") {
|
|
||||||
const p = device[exp[1]];
|
|
||||||
if (p && p.value) return p.value[0];
|
|
||||||
} else if (exp[0] === "FUNC") {
|
|
||||||
if (exp[1] === "REMOTE_ADDRESS") {
|
|
||||||
for (const root of ["InternetGatewayDevice", "Device"]) {
|
|
||||||
const p = device[`${root}.ManagementServer.ConnectionRequestURL`];
|
|
||||||
if (p && p.value) return url.parse(p.value[0]).host;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exp;
|
|
||||||
}
|
|
||||||
) as number;
|
|
||||||
|
|
||||||
const res = await apiFunctions.postTasks(
|
const res = await apiFunctions.postTasks(
|
||||||
ctx.params.id,
|
ctx.params.id,
|
||||||
ctx.request.body,
|
ctx.request.body,
|
||||||
onlineThreshold,
|
onlineThreshold
|
||||||
device
|
|
||||||
);
|
);
|
||||||
|
|
||||||
log.tasks = res.tasks.map((t) => t._id).join(",");
|
log.tasks = res.tasks.map(t => t._id).join(",");
|
||||||
|
|
||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
|
|
||||||
@@ -551,13 +520,13 @@ router.post("/devices/:id/tags", async (ctx, next) => {
|
|||||||
message: "Update tags",
|
message: "Update tags",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
deviceId: ctx.params.id,
|
deviceId: ctx.params.id,
|
||||||
tags: ctx.request.body,
|
tags: ctx.request.body
|
||||||
};
|
};
|
||||||
|
|
||||||
const filter = and(authorizer.getFilter("devices", 3), [
|
const filter = and(authorizer.getFilter("devices", 3), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", "DeviceID.ID"],
|
["PARAM", "DeviceID.ID"],
|
||||||
ctx.params.id,
|
ctx.params.id
|
||||||
]);
|
]);
|
||||||
if (!authorizer.hasAccess("devices", 3)) {
|
if (!authorizer.hasAccess("devices", 3)) {
|
||||||
logUnauthorizedWarning(log);
|
logUnauthorizedWarning(log);
|
||||||
@@ -586,8 +555,8 @@ router.post("/devices/:id/tags", async (ctx, next) => {
|
|||||||
ctx.body = "";
|
ctx.body = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/ping/:host", async (ctx) => {
|
router.get("/ping/:host", async ctx => {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
ping(ctx.params.host, (err, parsed) => {
|
ping(ctx.params.host, (err, parsed) => {
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
ctx.body = parsed;
|
ctx.body = parsed;
|
||||||
@@ -606,7 +575,7 @@ router.put("/users/:id/password", async (ctx, next) => {
|
|||||||
const log = {
|
const log = {
|
||||||
message: "Change password",
|
message: "Change password",
|
||||||
context: ctx,
|
context: ctx,
|
||||||
username: username,
|
username: username
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!ctx.state.user) {
|
if (!ctx.state.user) {
|
||||||
@@ -631,7 +600,7 @@ router.put("/users/:id/password", async (ctx, next) => {
|
|||||||
const filter = and(authorizer.getFilter("users", 3), [
|
const filter = and(authorizer.getFilter("users", 3), [
|
||||||
"=",
|
"=",
|
||||||
["PARAM", RESOURCE_IDS.users],
|
["PARAM", RESOURCE_IDS.users],
|
||||||
username,
|
username
|
||||||
]);
|
]);
|
||||||
const res = await db.query("users", filter);
|
const res = await db.query("users", filter);
|
||||||
if (!res.length) return void next();
|
if (!res.length) return void next();
|
||||||
|
|||||||
+121
-161
@@ -17,21 +17,19 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MongoClient, GridFSBucket, ObjectID } from "mongodb";
|
import { MongoClient, GridFSBucket } from "mongodb";
|
||||||
import { Script } from "vm";
|
import { Script } from "vm";
|
||||||
import * as config from "../config";
|
import * as config from "../config";
|
||||||
import * as mongodbFunctions from "../mongodb-functions";
|
import * as mongodbFunctions from "../mongodb-functions";
|
||||||
import * as expression from "../common/expression";
|
import * as expression from "../common/expression";
|
||||||
import { QueryOptions, Expression } from "../types";
|
import { QueryOptions } from "../types";
|
||||||
import { Readable } from "stream";
|
|
||||||
import { minimize } from "../common/boolean-expression";
|
|
||||||
|
|
||||||
const CACHE_TTL = 300000;
|
const CACHE_TTL = 300000;
|
||||||
|
|
||||||
let clientPromise: Promise<MongoClient>;
|
let clientPromise: Promise<MongoClient>;
|
||||||
|
|
||||||
const RESOURCE_COLLECTION = {
|
const RESOURCE_COLLECTION = {
|
||||||
files: "fs.files",
|
files: "fs.files"
|
||||||
};
|
};
|
||||||
|
|
||||||
function ensureIndexes(client): void {
|
function ensureIndexes(client): void {
|
||||||
@@ -47,7 +45,7 @@ function getClient(): Promise<MongoClient> {
|
|||||||
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, useUnifiedTopology: true },
|
{ useNewUrlParser: true },
|
||||||
(err, client) => {
|
(err, client) => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
ensureIndexes(client);
|
ensureIndexes(client);
|
||||||
@@ -60,31 +58,27 @@ function getClient(): Promise<MongoClient> {
|
|||||||
return clientPromise;
|
return clientPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cache<T>(
|
export function cache<T>(key, valueGetter: () => Promise<T>, ttl): Promise<T> {
|
||||||
key: string,
|
|
||||||
valueGetter: () => Promise<T>,
|
|
||||||
ttl: number
|
|
||||||
): Promise<T> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const collection = client.db().collection("cache");
|
const collection = client.db().collection("cache");
|
||||||
collection.findOne({ _id: key }, (err, doc) => {
|
collection.findOne({ _id: key }, (err, doc) => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
if (doc != null) return void resolve(JSON.parse(doc.value) as T);
|
if (doc != null) return void resolve(JSON.parse(doc.value) as T);
|
||||||
valueGetter()
|
valueGetter()
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
const expire = Date.now() + (ttl || CACHE_TTL);
|
const expire = Date.now() + (ttl || CACHE_TTL);
|
||||||
const cacheDoc = {
|
const cacheDoc = {
|
||||||
_id: key,
|
_id: key,
|
||||||
value: JSON.stringify(res),
|
value: JSON.stringify(res),
|
||||||
expire: new Date(expire),
|
expire: new Date(expire)
|
||||||
};
|
};
|
||||||
collection.updateOne(
|
collection.updateOne(
|
||||||
{ _id: key },
|
{ _id: key },
|
||||||
{ $set: cacheDoc },
|
{ $set: cacheDoc },
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else resolve(res);
|
else resolve(res);
|
||||||
}
|
}
|
||||||
@@ -98,15 +92,14 @@ export function cache<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function query(
|
export function query(
|
||||||
resource: string,
|
resource,
|
||||||
filter: Expression,
|
filter,
|
||||||
options?: QueryOptions,
|
options?: QueryOptions,
|
||||||
callback?: (doc: any) => void
|
callback?: (doc: any) => void
|
||||||
): Promise<any[]> {
|
): Promise<any[]> {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
let q;
|
let q;
|
||||||
filter = expression.evaluate(filter, null, Date.now());
|
filter = expression.evaluate(filter, null, Date.now());
|
||||||
filter = minimize(filter, true);
|
|
||||||
|
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
if (resource === "devices") {
|
if (resource === "devices") {
|
||||||
@@ -128,87 +121,84 @@ export function query(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient().then(client => {
|
||||||
.then((client) => {
|
const collection = client
|
||||||
const collection = client
|
.db()
|
||||||
.db()
|
.collection(RESOURCE_COLLECTION[resource] || resource);
|
||||||
.collection(RESOURCE_COLLECTION[resource] || resource);
|
const cursor = collection.find(q);
|
||||||
const cursor = collection.find(q);
|
if (options.projection) {
|
||||||
if (options.projection) {
|
let projection = options.projection;
|
||||||
let projection = options.projection;
|
if (resource === "devices") {
|
||||||
if (resource === "devices") {
|
projection = mongodbFunctions.processDeviceProjection(
|
||||||
projection = mongodbFunctions.processDeviceProjection(
|
options.projection
|
||||||
options.projection
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resource === "presets") projection.configurations = 1;
|
|
||||||
cursor.project(projection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resource === "users") cursor.project({ password: 0, salt: 0 });
|
|
||||||
|
|
||||||
if (options.skip) cursor.skip(options.skip);
|
|
||||||
if (options.limit) cursor.limit(options.limit);
|
|
||||||
|
|
||||||
if (options.sort) {
|
|
||||||
let s = Object.entries(options.sort)
|
|
||||||
.sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
|
|
||||||
.reduce(
|
|
||||||
(obj, [k, v]) =>
|
|
||||||
Object.assign(obj, { [k]: Math.min(Math.max(v, -1), 1) }),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resource === "devices") s = mongodbFunctions.processDeviceSort(s);
|
|
||||||
cursor.sort(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
cursor.toArray((err, docs) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
if (resource === "devices")
|
|
||||||
docs = docs.map((d) => mongodbFunctions.flattenDevice(d));
|
|
||||||
else if (resource === "faults")
|
|
||||||
docs = docs.map((d) => mongodbFunctions.flattenFault(d));
|
|
||||||
else if (resource === "tasks")
|
|
||||||
docs = docs.map((d) => mongodbFunctions.flattenTask(d));
|
|
||||||
else if (resource === "presets")
|
|
||||||
docs = docs.map((d) => mongodbFunctions.flattenPreset(d));
|
|
||||||
else if (resource === "files")
|
|
||||||
docs = docs.map((d) => mongodbFunctions.flattenFile(d));
|
|
||||||
return resolve(docs);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cursor.forEach(
|
|
||||||
(doc) => {
|
|
||||||
if (resource === "devices")
|
|
||||||
doc = mongodbFunctions.flattenDevice(doc);
|
|
||||||
else if (resource === "faults")
|
|
||||||
doc = mongodbFunctions.flattenFault(doc);
|
|
||||||
else if (resource === "tasks")
|
|
||||||
doc = mongodbFunctions.flattenTask(doc);
|
|
||||||
else if (resource === "presets")
|
|
||||||
doc = mongodbFunctions.flattenPreset(doc);
|
|
||||||
else if (resource === "files")
|
|
||||||
doc = mongodbFunctions.flattenFile(doc);
|
|
||||||
callback(doc);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve();
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(reject);
|
if (resource === "presets") projection.configurations = 1;
|
||||||
|
cursor.project(projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === "users") cursor.project({ password: 0, salt: 0 });
|
||||||
|
|
||||||
|
if (options.skip) cursor.skip(options.skip);
|
||||||
|
if (options.limit) cursor.limit(options.limit);
|
||||||
|
|
||||||
|
if (options.sort) {
|
||||||
|
let s = Object.entries(options.sort)
|
||||||
|
.sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
|
||||||
|
.reduce(
|
||||||
|
(obj, [k, v]) =>
|
||||||
|
Object.assign(obj, { [k]: Math.min(Math.max(v, -1), 1) }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resource === "devices") s = mongodbFunctions.processDeviceSort(s);
|
||||||
|
cursor.sort(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
cursor.toArray((err, docs) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
if (resource === "devices")
|
||||||
|
docs = docs.map(d => mongodbFunctions.flattenDevice(d));
|
||||||
|
else if (resource === "faults")
|
||||||
|
docs = docs.map(d => mongodbFunctions.flattenFault(d));
|
||||||
|
else if (resource === "tasks")
|
||||||
|
docs = docs.map(d => mongodbFunctions.flattenTask(d));
|
||||||
|
else if (resource === "presets")
|
||||||
|
docs = docs.map(d => mongodbFunctions.flattenPreset(d));
|
||||||
|
else if (resource === "files")
|
||||||
|
docs = docs.map(d => mongodbFunctions.flattenFile(d));
|
||||||
|
return resolve(docs);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cursor.forEach(
|
||||||
|
doc => {
|
||||||
|
if (resource === "devices")
|
||||||
|
doc = mongodbFunctions.flattenDevice(doc);
|
||||||
|
else if (resource === "faults")
|
||||||
|
doc = mongodbFunctions.flattenFault(doc);
|
||||||
|
else if (resource === "tasks")
|
||||||
|
doc = mongodbFunctions.flattenTask(doc);
|
||||||
|
else if (resource === "presets")
|
||||||
|
doc = mongodbFunctions.flattenPreset(doc);
|
||||||
|
else if (resource === "files")
|
||||||
|
doc = mongodbFunctions.flattenFile(doc);
|
||||||
|
callback(doc);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function count(resource: string, filter: Expression): Promise<number> {
|
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());
|
||||||
filter = minimize(filter, true);
|
|
||||||
|
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
if (resource === "devices")
|
if (resource === "devices")
|
||||||
@@ -223,24 +213,19 @@ export function count(resource: string, filter: Expression): Promise<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient().then(client => {
|
||||||
.then((client) => {
|
const collection = client
|
||||||
const collection = client
|
.db()
|
||||||
.db()
|
.collection(RESOURCE_COLLECTION[resource] || resource);
|
||||||
.collection(RESOURCE_COLLECTION[resource] || resource);
|
collection.find(q).count((err, c) => {
|
||||||
collection.find(q).count((err, c) => {
|
if (err) reject(err);
|
||||||
if (err) reject(err);
|
else resolve(c);
|
||||||
else resolve(c);
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDeviceTags(
|
export function updateDeviceTags(deviceId, tags): Promise<void> {
|
||||||
deviceId: string,
|
|
||||||
tags: Record<string, boolean>
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const add = [];
|
const add = [];
|
||||||
const pull = [];
|
const pull = [];
|
||||||
@@ -257,13 +242,13 @@ export function updateDeviceTags(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const collection = client.db().collection("devices");
|
const collection = client.db().collection("devices");
|
||||||
|
|
||||||
const object = {};
|
const object = {};
|
||||||
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
||||||
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
||||||
collection.updateOne({ _id: deviceId }, object, (err) => {
|
collection.updateOne({ _id: deviceId }, object, err => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -275,11 +260,11 @@ export function updateDeviceTags(
|
|||||||
function putResource(resource, id, object): Promise<void> {
|
function putResource(resource, id, object): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const collection = client
|
const collection = client
|
||||||
.db()
|
.db()
|
||||||
.collection(RESOURCE_COLLECTION[resource] || resource);
|
.collection(RESOURCE_COLLECTION[resource] || resource);
|
||||||
collection.replaceOne({ _id: id }, object, { upsert: true }, (err) => {
|
collection.replaceOne({ _id: id }, object, { upsert: true }, err => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -288,17 +273,14 @@ function putResource(resource, id, object): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteResource(
|
function deleteResource(resource, id): Promise<void> {
|
||||||
resource: string,
|
|
||||||
id: string | ObjectID
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const collection = client
|
const collection = client
|
||||||
.db()
|
.db()
|
||||||
.collection(RESOURCE_COLLECTION[resource] || resource);
|
.collection(RESOURCE_COLLECTION[resource] || resource);
|
||||||
collection.deleteOne({ _id: id }, (err) => {
|
collection.deleteOne({ _id: id }, err => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -307,22 +289,16 @@ function deleteResource(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putPreset(
|
export function putPreset(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
object = mongodbFunctions.preProcessPreset(object);
|
object = mongodbFunctions.preProcessPreset(object);
|
||||||
return putResource("presets", id, object);
|
return putResource("presets", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deletePreset(id: string): Promise<void> {
|
export function deletePreset(id): Promise<void> {
|
||||||
return deleteResource("presets", id);
|
return deleteResource("presets", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putProvision(
|
export function putProvision(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
if (!object.script) object.script = "";
|
if (!object.script) object.script = "";
|
||||||
try {
|
try {
|
||||||
new Script(`"use strict";(function(){\n${object.script}\n})();`);
|
new Script(`"use strict";(function(){\n${object.script}\n})();`);
|
||||||
@@ -332,14 +308,11 @@ export function putProvision(
|
|||||||
return putResource("provisions", id, object);
|
return putResource("provisions", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteProvision(id: string): Promise<void> {
|
export function deleteProvision(id): Promise<void> {
|
||||||
return deleteResource("provisions", id);
|
return deleteResource("provisions", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putVirtualParameter(
|
export function putVirtualParameter(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
if (!object.script) object.script = "";
|
if (!object.script) object.script = "";
|
||||||
try {
|
try {
|
||||||
new Script(`"use strict";(function(){\n${object.script}\n})();`);
|
new Script(`"use strict";(function(){\n${object.script}\n})();`);
|
||||||
@@ -349,46 +322,37 @@ export function putVirtualParameter(
|
|||||||
return putResource("virtualParameters", id, object);
|
return putResource("virtualParameters", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteVirtualParameter(id: string): Promise<void> {
|
export function deleteVirtualParameter(id): Promise<void> {
|
||||||
return deleteResource("virtualParameters", id);
|
return deleteResource("virtualParameters", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putConfig(
|
export function putConfig(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
return putResource("config", id, object);
|
return putResource("config", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteConfig(id: string): Promise<void> {
|
export function deleteConfig(id): Promise<void> {
|
||||||
return deleteResource("config", id);
|
return deleteResource("config", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putPermission(
|
export function putPermission(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
return putResource("permissions", id, object);
|
return putResource("permissions", id, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deletePermission(id: string): Promise<void> {
|
export function deletePermission(id): Promise<void> {
|
||||||
return deleteResource("permissions", id);
|
return deleteResource("permissions", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putUser(
|
export function putUser(id, object): Promise<void> {
|
||||||
id: string,
|
|
||||||
object: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const collection = client.db().collection("users");
|
const collection = client.db().collection("users");
|
||||||
// update instead of replace to keep the password if not set by user
|
// update instead of replace to keep the password if not set by user
|
||||||
collection.updateOne(
|
collection.updateOne(
|
||||||
{ _id: id },
|
{ _id: id },
|
||||||
{ $set: object },
|
{ $set: object },
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
(err) => {
|
err => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -398,21 +362,17 @@ export function putUser(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUser(id: string): Promise<void> {
|
export function deleteUser(id): Promise<void> {
|
||||||
return deleteResource("users", id);
|
return deleteResource("users", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putFile(
|
export function putFile(filename, metadata, contentStream): Promise<void> {
|
||||||
filename: string,
|
|
||||||
metadata: Record<string, string>,
|
|
||||||
contentStream: Readable
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const bucket = new GridFSBucket(client.db());
|
const bucket = new GridFSBucket(client.db());
|
||||||
const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
|
const uploadStream = bucket.openUploadStreamWithId(filename, filename, {
|
||||||
metadata: metadata,
|
metadata: metadata
|
||||||
});
|
});
|
||||||
uploadStream.on("error", reject);
|
uploadStream.on("error", reject);
|
||||||
contentStream.on("error", reject);
|
contentStream.on("error", reject);
|
||||||
@@ -423,12 +383,12 @@ export function putFile(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFile(filename: string): Promise<void> {
|
export function deleteFile(filename): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getClient()
|
getClient()
|
||||||
.then((client) => {
|
.then(client => {
|
||||||
const bucket = new GridFSBucket(client.db());
|
const bucket = new GridFSBucket(client.db());
|
||||||
bucket.delete(filename as any, (err) => {
|
bucket.delete(filename, err => {
|
||||||
if (err) return void reject(err);
|
if (err) return void reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -437,11 +397,11 @@ export function deleteFile(filename: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFault(id: string): Promise<void> {
|
export function deleteFault(id): Promise<void> {
|
||||||
return deleteResource("faults", id);
|
return deleteResource("faults", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteTask(id: ObjectID): Promise<void> {
|
export function deleteTask(id): Promise<void> {
|
||||||
return deleteResource("tasks", id);
|
return deleteResource("tasks", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default class VersionedMap<K, V> {
|
|||||||
return this._revision;
|
return this._revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set revision(rev: number) {
|
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];
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ export default class VersionedMap<K, V> {
|
|||||||
const last = v[l];
|
const last = v[l];
|
||||||
v.splice(revision, l - revision);
|
v.splice(revision, l - revision);
|
||||||
|
|
||||||
if (last === NONEXISTENT && !v.some((vv) => vv !== NONEXISTENT))
|
if (last === NONEXISTENT && !v.some(vv => vv !== NONEXISTENT))
|
||||||
this.map.delete(k);
|
this.map.delete(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -90,7 +90,7 @@ export function parseAttrs(string: string): Attribute[] {
|
|||||||
name: name,
|
name: name,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
localName: localName,
|
localName: localName,
|
||||||
value: value,
|
value: value
|
||||||
};
|
};
|
||||||
attrs.push(e);
|
attrs.push(e);
|
||||||
name = "";
|
name = "";
|
||||||
@@ -128,8 +128,8 @@ export function parseAttrs(string: string): Attribute[] {
|
|||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeEntities(string: string): string {
|
export function decodeEntities(string): string {
|
||||||
return string.replace(/&[0-9a-z#]+;/gi, (match) => {
|
return string.replace(/&[0-9a-z#]+;/gi, match => {
|
||||||
switch (match) {
|
switch (match) {
|
||||||
case """:
|
case """:
|
||||||
return '"';
|
return '"';
|
||||||
@@ -161,15 +161,15 @@ export function decodeEntities(string: string): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeEntities(string: string): string {
|
export function encodeEntities(string): string {
|
||||||
const entities = {
|
const entities = {
|
||||||
"&": "&",
|
"&": "&",
|
||||||
'"': """,
|
'"': """,
|
||||||
"'": "'",
|
"'": "'",
|
||||||
"<": "<",
|
"<": "<",
|
||||||
">": ">",
|
">": ">"
|
||||||
};
|
};
|
||||||
return string.replace(/[&"'<>]/g, (m) => entities[m]);
|
return string.replace(/[&"'<>]/g, m => entities[m]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseXml(string: string): Element {
|
export function parseXml(string: string): Element {
|
||||||
@@ -186,7 +186,7 @@ export function parseXml(string: string): Element {
|
|||||||
attrs: "",
|
attrs: "",
|
||||||
text: "",
|
text: "",
|
||||||
bodyIndex: 0,
|
bodyIndex: 0,
|
||||||
children: [],
|
children: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const stack: Element[] = [root];
|
const stack: Element[] = [root];
|
||||||
@@ -330,7 +330,7 @@ export function parseXml(string: string): Element {
|
|||||||
: "",
|
: "",
|
||||||
text: "",
|
text: "",
|
||||||
bodyIndex: i + 1,
|
bodyIndex: i + 1,
|
||||||
children: [],
|
children: []
|
||||||
};
|
};
|
||||||
parent.children.push(e);
|
parent.children.push(e);
|
||||||
if (!selfClosing) stack.push(e);
|
if (!selfClosing) stack.push(e);
|
||||||
|
|||||||
Generated
+3793
-2575
File diff suppressed because it is too large.
Load diff
+46
-45
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.1",
|
"version": "1.2.0-beta.0",
|
||||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -20,68 +20,69 @@
|
|||||||
"tools": "tools"
|
"tools": "tools"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"espresso-iisojs": "^1.0.3",
|
"iconv-lite": "^0.5.0",
|
||||||
"iconv-lite": "^0.6.2",
|
|
||||||
"ipaddr.js": "^2.0.0",
|
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"koa": "^2.13.0",
|
"koa": "^2.7.0",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.2.1",
|
||||||
"koa-compress": "^5.0.1",
|
"koa-compress": "^3.0.0",
|
||||||
"koa-jwt": "^4.0.0",
|
"koa-jwt": "^3.6.0",
|
||||||
"koa-router": "^9.4.0",
|
"koa-router": "^7.4.0",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"later": "^1.2.0",
|
"later": "^1.2.0",
|
||||||
"mongodb": "^3.6.1",
|
"mongodb": "^3.2.6",
|
||||||
"parsimmon": "^1.16.0",
|
"parsimmon": "^1.13.0",
|
||||||
"seedrandom": "^3.0.5",
|
"seedrandom": "^3.0.1",
|
||||||
"yaml": "^1.10.0"
|
"yaml": "^1.6.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"libxmljs": "^0.19.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^15.0.0",
|
"@types/jsonwebtoken": "^8.3.2",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@types/koa": "^2.0.49",
|
||||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
"@types/koa-compress": "^2.0.9",
|
||||||
"@rollup/plugin-typescript": "^5.0.2",
|
"@types/koa-router": "^7.0.42",
|
||||||
"@types/jsonwebtoken": "^8.5.0",
|
|
||||||
"@types/koa": "^2.11.4",
|
|
||||||
"@types/koa-compress": "^4.0.0",
|
|
||||||
"@types/koa-router": "^7.4.1",
|
|
||||||
"@types/koa-static": "^4.0.1",
|
"@types/koa-static": "^4.0.1",
|
||||||
"@types/mithril": "^2.0.3",
|
"@types/libxmljs": "^0.18.3",
|
||||||
"@types/mongodb": "^3.5.27",
|
"@types/mithril": "^2.0.0",
|
||||||
"@types/parsimmon": "^1.10.2",
|
"@types/mongodb": "^3.1.31",
|
||||||
|
"@types/parsimmon": "^1.10.0",
|
||||||
"@types/seedrandom": "^2.4.28",
|
"@types/seedrandom": "^2.4.28",
|
||||||
"@types/svgo": "^1.3.3",
|
"@types/svgo": "^1.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||||
"@typescript-eslint/parser": "^3.10.1",
|
"@typescript-eslint/parser": "^1.13.0",
|
||||||
"ava": "^3.12.1",
|
"ava": "^2.2.0",
|
||||||
"codemirror": "^5.57.0",
|
"codemirror": "^5.48.2",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"eslint": "^7.8.0",
|
"eslint": "^6.1.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.0.0",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"jsbi": "^3.1.3",
|
"mithril": "^2.0.3",
|
||||||
"mithril": "^2.0.4",
|
"postcss": "^7.0.17",
|
||||||
"postcss": "^7.0.32",
|
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^1.18.2",
|
||||||
"rollup": "^2.26.9",
|
"rollup": "^1.17.0",
|
||||||
"rollup-plugin-terser": "^7.0.1",
|
"rollup-plugin-json": "^4.0.0",
|
||||||
"source-map-loader": "^1.1.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svgo": "^1.3.2",
|
"rollup-plugin-terser": "^5.1.1",
|
||||||
"ts-node": "^9.0.0",
|
"rollup-plugin-typescript": "^1.0.1",
|
||||||
"typescript": "^4.0.2",
|
"source-map-loader": "^0.2.4",
|
||||||
"webpack": "^4.44.1"
|
"svgo": "^1.3.0",
|
||||||
|
"ts-node": "^8.3.0",
|
||||||
|
"typescript": "^3.5.3",
|
||||||
|
"webpack": "^4.38.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.13.0"
|
"node": ">=8.14.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "ava --verbose --color",
|
"test": "ava --verbose --color",
|
||||||
"lint-eslint": "eslint --fix \"bin/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/*.ts\"",
|
"lint-ln": "find bin -type f -exec sh -c 'ln -fs \"$(sh -c pwd)/$1\" \"bin/.${1#bin/}.ts\"' _ {} \\;; find tools -type f -exec sh -c 'ln -fs \"$(sh -c pwd)/$1\" \"tools/.${1#tools/}.ts\"' _ {} \\;",
|
||||||
|
"lint-eslint": "eslint --fix \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"",
|
||||||
"lint-prettier": "prettier --parser typescript --write \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"",
|
"lint-prettier": "prettier --parser typescript --write \"bin/**/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\" \"tools/**/*.ts\"",
|
||||||
"lint-tsc": "tsc --noEmit",
|
"lint-tsc": "tsc --noEmit",
|
||||||
"lint": "npm run lint-eslint && npm run lint-prettier && npm run lint-tsc",
|
"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"
|
"build": "node -r esm -r ts-node/register/transpile-only build/build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -2,7 +2,7 @@ import ava from "ava";
|
|||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import * as auth from "../lib/auth";
|
import * as auth from "../lib/auth";
|
||||||
|
|
||||||
ava("digest", (t) => {
|
ava("digest", t => {
|
||||||
const username = "test";
|
const username = "test";
|
||||||
const password = "test";
|
const password = "test";
|
||||||
const uri = "/";
|
const uri = "/";
|
||||||
@@ -14,7 +14,7 @@ ava("digest", (t) => {
|
|||||||
const challenges = [
|
const challenges = [
|
||||||
`Digest realm="${realm}",nonce="${nonce}"`,
|
`Digest realm="${realm}",nonce="${nonce}"`,
|
||||||
`Digest realm="${realm}",nonce="${nonce}",qop="auth"`,
|
`Digest realm="${realm}",nonce="${nonce}",qop="auth"`,
|
||||||
`Digest realm="${realm}",nonce="${nonce}",qop="auth-int"`,
|
`Digest realm="${realm}",nonce="${nonce}",qop="auth-int"`
|
||||||
];
|
];
|
||||||
|
|
||||||
t.plan(challenges.length);
|
t.plan(challenges.length);
|
||||||
|
|||||||
+38
-11
@@ -1,7 +1,35 @@
|
|||||||
import ava from "ava";
|
import ava from "ava";
|
||||||
import * as common from "../lib/common";
|
import * as common from "../lib/common";
|
||||||
|
|
||||||
ava("generateDeviceId", (t) => {
|
ava("typeOf", t => {
|
||||||
|
const cases = [
|
||||||
|
[undefined, common.UNDEFINED_TYPE],
|
||||||
|
[null, common.NULL_TYPE],
|
||||||
|
[true, common.BOOLEAN_TYPE],
|
||||||
|
[false, common.BOOLEAN_TYPE],
|
||||||
|
[0, common.NUMBER_TYPE],
|
||||||
|
[1, common.NUMBER_TYPE],
|
||||||
|
[-1, common.NUMBER_TYPE],
|
||||||
|
[0.0, common.NUMBER_TYPE],
|
||||||
|
[1.0, common.NUMBER_TYPE],
|
||||||
|
[1.1, common.NUMBER_TYPE],
|
||||||
|
[-1.0, common.NUMBER_TYPE],
|
||||||
|
[-1.1, common.NUMBER_TYPE],
|
||||||
|
["", common.STRING_TYPE],
|
||||||
|
["TEST", common.STRING_TYPE],
|
||||||
|
[[], common.ARRAY_TYPE],
|
||||||
|
[[1, 2, 3], common.ARRAY_TYPE],
|
||||||
|
[{}, common.OBJECT_TYPE],
|
||||||
|
[{ test: 1 }, common.OBJECT_TYPE],
|
||||||
|
[/[^A-Za-z0-9_]/g, common.REGEXP_TYPE],
|
||||||
|
[new RegExp("//"), common.REGEXP_TYPE],
|
||||||
|
[new Date(), common.DATE_TYPE]
|
||||||
|
];
|
||||||
|
t.plan(cases.length);
|
||||||
|
for (const c of cases) t.is(common.typeOf(c[0]), c[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
ava("generateDeviceId", t => {
|
||||||
const space = [" ", "%20"];
|
const space = [" ", "%20"];
|
||||||
const special = [";", "%3B"];
|
const special = [";", "%3B"];
|
||||||
const cases = [
|
const cases = [
|
||||||
@@ -9,31 +37,30 @@ ava("generateDeviceId", (t) => {
|
|||||||
{
|
{
|
||||||
ProductClass: "TestProductClass",
|
ProductClass: "TestProductClass",
|
||||||
OUI: "TestOUI",
|
OUI: "TestOUI",
|
||||||
SerialNumber: "TestSerialNumber",
|
SerialNumber: "TestSerialNumber"
|
||||||
},
|
},
|
||||||
"TestOUI-TestProductClass-TestSerialNumber",
|
"TestOUI-TestProductClass-TestSerialNumber"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
OUI: "TestOUI",
|
OUI: "TestOUI",
|
||||||
SerialNumber: "TestSerialNumber",
|
SerialNumber: "TestSerialNumber"
|
||||||
},
|
},
|
||||||
"TestOUI-TestSerialNumber",
|
"TestOUI-TestSerialNumber"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
OUI: `TestOUIWith${space[0]}_${special[0]}2912`,
|
OUI: `TestOUIWith${space[0]}_${special[0]}2912`,
|
||||||
SerialNumber: `TestSerialNumberWith${space[0]}_${special[0]}2912`,
|
SerialNumber: `TestSerialNumberWith${space[0]}_${special[0]}2912`
|
||||||
},
|
},
|
||||||
`TestOUIWith${space[1]}_${special[1]}2912-TestSerialNumberWith${space[1]}_${special[1]}2912`,
|
`TestOUIWith${space[1]}_${special[1]}2912-TestSerialNumberWith${space[1]}_${special[1]}2912`
|
||||||
],
|
]
|
||||||
];
|
];
|
||||||
t.plan(cases.length);
|
t.plan(cases.length);
|
||||||
for (const c of cases)
|
for (const c of cases) t.is(common.generateDeviceId(c[0]), c[1]);
|
||||||
t.is(common.generateDeviceId(c[0] as Record<string, string>), c[1]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("escapeRegExp", (t) => {
|
ava("escapeRegExp", t => {
|
||||||
t.is(
|
t.is(
|
||||||
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
|
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
|
||||||
"\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]"
|
"\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]"
|
||||||
|
|||||||
+19
-19
@@ -5,7 +5,7 @@ import PathSet from "../lib/common/path-set";
|
|||||||
import VersionedMap from "../lib/versioned-map";
|
import VersionedMap from "../lib/versioned-map";
|
||||||
import { Attributes } from "../lib/types";
|
import { Attributes } from "../lib/types";
|
||||||
|
|
||||||
ava("getAliasDeclarations", (t) => {
|
ava("getAliasDeclarations", t => {
|
||||||
const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b");
|
const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b");
|
||||||
const decs = device.getAliasDeclarations(path, 99);
|
const decs = device.getAliasDeclarations(path, 99);
|
||||||
|
|
||||||
@@ -22,64 +22,64 @@ ava("getAliasDeclarations", (t) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("unpack", (t) => {
|
ava("unpack", t => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const deviceData = {
|
const deviceData = {
|
||||||
paths: new PathSet(),
|
paths: new PathSet(),
|
||||||
timestamps: new VersionedMap<Path, number>(),
|
timestamps: new VersionedMap<Path, number>(),
|
||||||
attributes: new VersionedMap<Path, Attributes>(),
|
attributes: new VersionedMap<Path, Attributes>(),
|
||||||
trackers: new Map<Path, { [name: string]: number }>(),
|
trackers: new Map<Path, { [name: string]: number }>(),
|
||||||
changes: new Set<string>(),
|
changes: new Set<string>()
|
||||||
};
|
};
|
||||||
|
|
||||||
device.set(deviceData, Path.parse("a.1.b"), now, {
|
device.set(deviceData, Path.parse("a.1.b"), now, {
|
||||||
value: [now, ["b", "xsd:string"]],
|
value: [now, ["b", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.c"), now, {
|
device.set(deviceData, Path.parse("a.1.c"), now, {
|
||||||
value: [now, ["c", "xsd:string"]],
|
value: [now, ["c", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.1.a"), now, {
|
device.set(deviceData, Path.parse("a.1.a.1.a"), now, {
|
||||||
value: [now, ["", "xsd:string"]],
|
value: [now, ["", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.1.b"), now, {
|
device.set(deviceData, Path.parse("a.1.a.1.b"), now, {
|
||||||
value: [now, ["b1", "xsd:string"]],
|
value: [now, ["b1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.1.c"), now, {
|
device.set(deviceData, Path.parse("a.1.a.1.c"), now, {
|
||||||
value: [now, ["c1", "xsd:string"]],
|
value: [now, ["c1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.2.a"), now, {
|
device.set(deviceData, Path.parse("a.1.a.2.a"), now, {
|
||||||
value: [now, ["", "xsd:string"]],
|
value: [now, ["", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.2.b"), now, {
|
device.set(deviceData, Path.parse("a.1.a.2.b"), now, {
|
||||||
value: [now, ["b2", "xsd:string"]],
|
value: [now, ["b2", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.1.a.2.c"), now, {
|
device.set(deviceData, Path.parse("a.1.a.2.c"), now, {
|
||||||
value: [now, ["c2", "xsd:string"]],
|
value: [now, ["c2", "xsd:string"]]
|
||||||
});
|
});
|
||||||
|
|
||||||
device.set(deviceData, Path.parse("a.2.b"), now, {
|
device.set(deviceData, Path.parse("a.2.b"), now, {
|
||||||
value: [now, ["b", "xsd:string"]],
|
value: [now, ["b", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.c"), now, {
|
device.set(deviceData, Path.parse("a.2.c"), now, {
|
||||||
value: [now, ["c", "xsd:string"]],
|
value: [now, ["c", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.1.a"), now, {
|
device.set(deviceData, Path.parse("a.2.a.1.a"), now, {
|
||||||
value: [now, ["", "xsd:string"]],
|
value: [now, ["", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.1.b"), now, {
|
device.set(deviceData, Path.parse("a.2.a.1.b"), now, {
|
||||||
value: [now, ["b1", "xsd:string"]],
|
value: [now, ["b1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.1.c"), now, {
|
device.set(deviceData, Path.parse("a.2.a.1.c"), now, {
|
||||||
value: [now, ["c1", "xsd:string"]],
|
value: [now, ["c1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.2.a"), now, {
|
device.set(deviceData, Path.parse("a.2.a.2.a"), now, {
|
||||||
value: [now, ["", "xsd:string"]],
|
value: [now, ["", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.2.b"), now, {
|
device.set(deviceData, Path.parse("a.2.a.2.b"), now, {
|
||||||
value: [now, ["c1", "xsd:string"]],
|
value: [now, ["c1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
device.set(deviceData, Path.parse("a.2.a.2.c"), now, {
|
device.set(deviceData, Path.parse("a.2.a.2.c"), now, {
|
||||||
value: [now, ["b1", "xsd:string"]],
|
value: [now, ["b1", "xsd:string"]]
|
||||||
});
|
});
|
||||||
|
|
||||||
let unpacked: Path[];
|
let unpacked: Path[];
|
||||||
|
|||||||
+12
-82
@@ -1,13 +1,13 @@
|
|||||||
import ava from "ava";
|
import ava from "ava";
|
||||||
import * as mongodbFunctions from "../lib/mongodb-functions";
|
import * as mongodbFunctions from "../lib/mongodb-functions";
|
||||||
import { stringify, parse } from "../lib/common/expression-parser";
|
import { stringify } from "../lib/common/expression-parser";
|
||||||
|
|
||||||
ava("convertOldPrecondition", (t) => {
|
ava("mongoQueryToFilter", t => {
|
||||||
const tests = [
|
const tests = [
|
||||||
[{}, "true"],
|
[{}, "true"],
|
||||||
[{ test: "test" }, 'test = "test"'],
|
[{ test: "test" }, 'test = "test"'],
|
||||||
[{ test: { $eq: "test" } }, 'test = "test"'],
|
[{ test: { $eq: "test" } }, 'test = "test"'],
|
||||||
[{ test: { $ne: "test" } }, 'test <> "test" OR test IS NULL'],
|
[{ test: { $ne: "test" } }, 'test <> "test"'],
|
||||||
[{ test: { $gte: "test" } }, 'test >= "test"'],
|
[{ test: { $gte: "test" } }, 'test >= "test"'],
|
||||||
[{ "Tags.test": true }, "Tags.test IS NOT NULL"],
|
[{ "Tags.test": true }, "Tags.test IS NOT NULL"],
|
||||||
[{ "Tags.test": false }, "Tags.test IS NULL"],
|
[{ "Tags.test": false }, "Tags.test IS NULL"],
|
||||||
@@ -22,21 +22,21 @@ ava("convertOldPrecondition", (t) => {
|
|||||||
[{ _tags: { $eq: "test" } }, "Tags.test IS NOT NULL"],
|
[{ _tags: { $eq: "test" } }, "Tags.test IS NOT NULL"],
|
||||||
[
|
[
|
||||||
{ $and: [{ test: "test" }, { test: { $ne: "test" } }] },
|
{ $and: [{ test: "test" }, { test: { $ne: "test" } }] },
|
||||||
'test = "test" AND (test <> "test" OR test IS NULL)',
|
'test = "test" AND test <> "test"'
|
||||||
],
|
],
|
||||||
[{ test: "test", test2: "test2" }, 'test = "test" AND test2 = "test2"'],
|
[{ test: "test", test2: "test2" }, 'test = "test" AND test2 = "test2"'],
|
||||||
[
|
[
|
||||||
{ $or: [{ test: "test" }, { test: { $ne: "test" } }] },
|
{ $or: [{ test: "test" }, { test: { $ne: "test" } }] },
|
||||||
'test = "test" OR (test <> "test" OR test IS NULL)',
|
'test = "test" OR test <> "test"'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ test: { $gte: "test1", $ne: "test2" } },
|
{ test: { $gte: "test1", $ne: "test2" } },
|
||||||
'test >= "test1" AND (test <> "test2" OR test IS NULL)',
|
'test >= "test1" AND test <> "test2"'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ test: "test", test2: { $ne: "test2" } },
|
{ test: "test", test2: { $ne: "test2" } },
|
||||||
'test = "test" AND (test2 <> "test2" OR test2 IS NULL)',
|
'test = "test" AND test2 <> "test2"'
|
||||||
],
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
const shouldFailTests = [
|
const shouldFailTests = [
|
||||||
@@ -45,88 +45,18 @@ ava("convertOldPrecondition", (t) => {
|
|||||||
[{ "Tags.test": { $gt: true } }, "Invalid tag query"],
|
[{ "Tags.test": { $gt: true } }, "Invalid tag query"],
|
||||||
[{ _tags: [] }, "Invalid type"],
|
[{ _tags: [] }, "Invalid type"],
|
||||||
[{ _tags: { $gt: "test" } }, "Invalid tag query"],
|
[{ _tags: { $gt: "test" } }, "Invalid tag query"],
|
||||||
[{ $nor: [] }, "Operator $nor not supported"],
|
[{ $nor: [] }, "Operator $nor not supported"]
|
||||||
];
|
];
|
||||||
|
|
||||||
t.plan(tests.length + 2 * shouldFailTests.length);
|
t.plan(tests.length + 2 * shouldFailTests.length);
|
||||||
for (const test of tests)
|
for (const test of tests)
|
||||||
t.is(stringify(mongodbFunctions.convertOldPrecondition(test[0])), test[1]);
|
t.is(stringify(mongodbFunctions.mongoQueryToFilter(test[0])), test[1]);
|
||||||
|
|
||||||
for (const test of shouldFailTests) {
|
for (const test of shouldFailTests) {
|
||||||
const func = (): void => {
|
const func = (): void => {
|
||||||
mongodbFunctions.convertOldPrecondition(
|
mongodbFunctions.mongoQueryToFilter(test[0]);
|
||||||
test[0] as Record<string, unknown>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
const error = t.throws(func, { instanceOf: Error });
|
const error = t.throws(func, Error);
|
||||||
t.is(error.message, test[1]);
|
t.is(error.message, test[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("filterToMongoQuery", async (t) => {
|
|
||||||
const queries: [string, Record<string, unknown>][] = [
|
|
||||||
["true", {}],
|
|
||||||
["Tags.tag1 = true", { _tags: "tag1" }],
|
|
||||||
["Tags.tag1 <> false", { _tags: "tag1" }],
|
|
||||||
["Tags.tag1 IS NULL", { _tags: { $ne: "tag1" } }],
|
|
||||||
["Tags.tag1 = 123", { "Tags.tag1": 123 }],
|
|
||||||
["Param1 = 'value1'", { "Param1._value": "value1" }],
|
|
||||||
["Param1 <> 'value1'", { "Param1._value": { $nin: ["value1", null] } }],
|
|
||||||
["Param1 > 'value'", { "Param1._value": { $gt: "value" } }],
|
|
||||||
["Param1 IS NOT NULL", { "Param1._value": { $ne: null } }],
|
|
||||||
["Param1 LIKE 'value'", { "Param1._value": /^value$/ }],
|
|
||||||
["LOWER(Param1) LIKE 'value'", { "Param1._value": /^value$/i }],
|
|
||||||
[
|
|
||||||
"Param1 <> 'value2' OR NOT (Param2 = 'value1' OR Param1 < 'value2')",
|
|
||||||
{
|
|
||||||
$or: [
|
|
||||||
{ "Param1._value": { $nin: ["value2", null] } },
|
|
||||||
{
|
|
||||||
$and: [
|
|
||||||
{ "Param2._value": { $nin: ["value1", null] } },
|
|
||||||
{ "Param1._value": { $gte: "value2" } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Param1 <> 'value2' OR Param1 IS NULL",
|
|
||||||
{
|
|
||||||
$or: [
|
|
||||||
{ "Param1._value": { $nin: ["value2", null] } },
|
|
||||||
{ "Param1._value": null },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [expStr, expect] of queries) {
|
|
||||||
const exp = parse(expStr);
|
|
||||||
const query = mongodbFunctions.filterToMongoQuery(
|
|
||||||
mongodbFunctions.processDeviceFilter(exp)
|
|
||||||
);
|
|
||||||
t.deepEqual(query, expect);
|
|
||||||
}
|
|
||||||
|
|
||||||
const failQueries: [any, string][] = [
|
|
||||||
["Param1 = Param2", "Invalid RHS operand of = clause"],
|
|
||||||
["Param1 LIKE Param2", "Invalid RHS operand of LIKE clause"],
|
|
||||||
["1 LIKE '1'", "Invalid LHS operand of LIKE clause"],
|
|
||||||
["1 = 2", "Invalid LHS operand of = clause"],
|
|
||||||
["1 IS NULL", "Invalid LHS operand of IS NULL clause"],
|
|
||||||
["false", "Primitives are not valid queries"],
|
|
||||||
["UPPER(Param1) LIKE 'value'", "Invalid RHS operand of LIKE clause"],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [expStr, err] of failQueries) {
|
|
||||||
const exp = parse(expStr);
|
|
||||||
t.throws(
|
|
||||||
() =>
|
|
||||||
mongodbFunctions.filterToMongoQuery(
|
|
||||||
mongodbFunctions.processDeviceFilter(exp)
|
|
||||||
),
|
|
||||||
{ message: err }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
+10
-10
@@ -2,7 +2,7 @@ import ava from "ava";
|
|||||||
import Path from "../lib/common/path";
|
import Path from "../lib/common/path";
|
||||||
import PathSet from "../lib/common/path-set";
|
import PathSet from "../lib/common/path-set";
|
||||||
|
|
||||||
ava("depth", (t) => {
|
ava("depth", t => {
|
||||||
const pathSet = new PathSet();
|
const pathSet = new PathSet();
|
||||||
t.is(pathSet.depth, 0);
|
t.is(pathSet.depth, 0);
|
||||||
pathSet.add(Path.parse(""));
|
pathSet.add(Path.parse(""));
|
||||||
@@ -11,14 +11,14 @@ ava("depth", (t) => {
|
|||||||
t.is(pathSet.depth, 2);
|
t.is(pathSet.depth, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("add", (t) => {
|
ava("add", t => {
|
||||||
const pathSet = new PathSet();
|
const pathSet = new PathSet();
|
||||||
pathSet.add(Path.parse("a"));
|
pathSet.add(Path.parse("a"));
|
||||||
pathSet.add(Path.parse("a"));
|
pathSet.add(Path.parse("a"));
|
||||||
t.is(pathSet.find(Path.parse("a"), true, true, 99).length, 1);
|
t.is(pathSet.find(Path.parse("a"), true, true, 99).length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("get", (t) => {
|
ava("get", t => {
|
||||||
const pathSet = new PathSet();
|
const pathSet = new PathSet();
|
||||||
pathSet.add(Path.parse("a.*"));
|
pathSet.add(Path.parse("a.*"));
|
||||||
pathSet.add(Path.parse("a.a"));
|
pathSet.add(Path.parse("a.a"));
|
||||||
@@ -28,7 +28,7 @@ ava("get", (t) => {
|
|||||||
t.is(pathSet.get(Path.parse("*.a")), null);
|
t.is(pathSet.get(Path.parse("*.a")), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("find", (t) => {
|
ava("find", t => {
|
||||||
const pathSet = new PathSet();
|
const pathSet = new PathSet();
|
||||||
pathSet.add(Path.parse("a"));
|
pathSet.add(Path.parse("a"));
|
||||||
pathSet.add(Path.parse("a.*"));
|
pathSet.add(Path.parse("a.*"));
|
||||||
@@ -37,32 +37,32 @@ ava("find", (t) => {
|
|||||||
pathSet.add(Path.parse("*.*"));
|
pathSet.add(Path.parse("*.*"));
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse(""), true, true, 1).map((p) => p.toString()),
|
pathSet.find(Path.parse(""), true, true, 1).map(p => p.toString()),
|
||||||
["a"]
|
["a"]
|
||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse(""), false, false, 2).map((p) => p.toString()),
|
pathSet.find(Path.parse(""), false, false, 2).map(p => p.toString()),
|
||||||
["a", "a.*", "a.a", "*.a", "*.*"]
|
["a", "a.*", "a.a", "*.a", "*.*"]
|
||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse("a.*"), false, false).map((p) => p.toString()),
|
pathSet.find(Path.parse("a.*"), false, false).map(p => p.toString()),
|
||||||
["a.*"]
|
["a.*"]
|
||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse("a.*"), false, true).map((p) => p.toString()),
|
pathSet.find(Path.parse("a.*"), false, true).map(p => p.toString()),
|
||||||
["a.*", "a.a"]
|
["a.*", "a.a"]
|
||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse("a.*"), true, false).map((p) => p.toString()),
|
pathSet.find(Path.parse("a.*"), true, false).map(p => p.toString()),
|
||||||
["a.*", "*.*"]
|
["a.*", "*.*"]
|
||||||
);
|
);
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
pathSet.find(Path.parse("a.*"), true, true).map((p) => p.toString()),
|
pathSet.find(Path.parse("a.*"), true, true).map(p => p.toString()),
|
||||||
["a.*", "a.a", "*.a", "*.*"]
|
["a.*", "a.a", "*.a", "*.*"]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
+3
-3
@@ -1,13 +1,13 @@
|
|||||||
import ava from "ava";
|
import ava from "ava";
|
||||||
import Path from "../lib/common/path";
|
import Path from "../lib/common/path";
|
||||||
|
|
||||||
ava("toString", (t) => {
|
ava("toString", t => {
|
||||||
const path1 = Path.parse('abc.[ abc : 123 , 123 : abc , 123: " abc "].123');
|
const path1 = Path.parse('abc.[ abc : 123 , 123 : abc , 123: " abc "].123');
|
||||||
const path2 = Path.parse('abc.[123: " abc ",abc:123,123:abc].123');
|
const path2 = Path.parse('abc.[123: " abc ",abc:123,123:abc].123');
|
||||||
t.is(path1.toString(), path2.toString());
|
t.is(path1.toString(), path2.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("slice", (t) => {
|
ava("slice", t => {
|
||||||
const path = Path.parse("a.*.b.[x:y].c");
|
const path = Path.parse("a.*.b.[x:y].c");
|
||||||
const sliced = path.slice(1, -1);
|
const sliced = path.slice(1, -1);
|
||||||
t.is(sliced.toString(), '*.b.[x:"y"]');
|
t.is(sliced.toString(), '*.b.[x:"y"]');
|
||||||
@@ -15,7 +15,7 @@ ava("slice", (t) => {
|
|||||||
t.is(sliced.wildcard, 0b1);
|
t.is(sliced.wildcard, 0b1);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("concat", (t) => {
|
ava("concat", t => {
|
||||||
const path1 = Path.parse("a");
|
const path1 = Path.parse("a");
|
||||||
const path2 = Path.parse("*.[a:b]");
|
const path2 = Path.parse("*.[a:b]");
|
||||||
const concat = path1.concat(path2);
|
const concat = path1.concat(path2);
|
||||||
|
|||||||
+13
-13
@@ -2,10 +2,10 @@ import ava from "ava";
|
|||||||
import {
|
import {
|
||||||
parseXmlDeclaration,
|
parseXmlDeclaration,
|
||||||
decodeEntities,
|
decodeEntities,
|
||||||
parseXml,
|
parseXml
|
||||||
} from "../lib/xml-parser";
|
} from "../lib/xml-parser";
|
||||||
|
|
||||||
ava("parseXmlDeclaration", (t) => {
|
ava("parseXmlDeclaration", t => {
|
||||||
const buf = Buffer.from(
|
const buf = Buffer.from(
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>\n<soap-env:Envelope />'
|
'<?xml version="1.0" encoding="UTF-8"?>\n<soap-env:Envelope />'
|
||||||
);
|
);
|
||||||
@@ -15,25 +15,25 @@ ava("parseXmlDeclaration", (t) => {
|
|||||||
name: "version",
|
name: "version",
|
||||||
namespace: "",
|
namespace: "",
|
||||||
localName: "version",
|
localName: "version",
|
||||||
value: "1.0",
|
value: "1.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "encoding",
|
name: "encoding",
|
||||||
namespace: "",
|
namespace: "",
|
||||||
localName: "encoding",
|
localName: "encoding",
|
||||||
value: "UTF-8",
|
value: "UTF-8"
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("decodeEntities", (t) => {
|
ava("decodeEntities", t => {
|
||||||
t.is(
|
t.is(
|
||||||
decodeEntities("&&<>"'>§��;"),
|
decodeEntities("&&<>"'>§��;"),
|
||||||
"&&<>\"'>§𠮷;"
|
"&&<>\"'>§𠮷;"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ava("parse", (t) => {
|
ava("parse", t => {
|
||||||
const xml =
|
const xml =
|
||||||
'<?xml version="1.0"?>\n<a-b:c><d f="1<g>"/><!-- comment --><h >i</h></a-b:c>';
|
'<?xml version="1.0"?>\n<a-b:c><d f="1<g>"/><!-- comment --><h >i</h></a-b:c>';
|
||||||
const parsed = parseXml(xml);
|
const parsed = parseXml(xml);
|
||||||
@@ -60,7 +60,7 @@ ava("parse", (t) => {
|
|||||||
attrs: 'f="1<g>"',
|
attrs: 'f="1<g>"',
|
||||||
text: "",
|
text: "",
|
||||||
bodyIndex: 42,
|
bodyIndex: 42,
|
||||||
children: [],
|
children: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "h",
|
name: "h",
|
||||||
@@ -69,10 +69,10 @@ ava("parse", (t) => {
|
|||||||
attrs: "",
|
attrs: "",
|
||||||
text: "i",
|
text: "i",
|
||||||
bodyIndex: 62,
|
bodyIndex: 62,
|
||||||
children: [],
|
children: []
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S node -r esm -r ts-node/register/transpile-only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013-2019 GenieACS Inc.
|
* Copyright 2013-2019 GenieACS Inc.
|
||||||
*
|
*
|
||||||
@@ -58,7 +60,7 @@ connect()
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
devicesCollection.findOne(
|
devicesCollection.findOne(
|
||||||
{
|
{
|
||||||
_id: process.argv[2],
|
_id: process.argv[2]
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
(err, device) => {
|
(err, device) => {
|
||||||
@@ -77,6 +79,6 @@ connect()
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
+2
-2
@@ -14,11 +14,11 @@
|
|||||||
"allowJs": true
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./bin/*.ts",
|
"./bin/.*.ts",
|
||||||
"./lib/**/*",
|
"./lib/**/*",
|
||||||
"./ui/**/*",
|
"./ui/**/*",
|
||||||
"./test/**/*",
|
"./test/**/*",
|
||||||
"./build/**/*",
|
"./build/**/*",
|
||||||
"./tools/*.ts"
|
"./tools/.*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+2
-2
@@ -22,7 +22,7 @@ import { ClosureComponent, Component } from "mithril";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const active = { [vnode.attrs["page"]]: "active" };
|
const active = { [vnode.attrs["page"]]: "active" };
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return m("nav#side-menu", m("ul", tabs));
|
return m("nav#side-menu", m("ul", tabs));
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,13 @@ import * as usersPage from "./users-page";
|
|||||||
import Authorizer from "../lib//common/authorizer";
|
import Authorizer from "../lib//common/authorizer";
|
||||||
import * as notifications from "./notifications";
|
import * as notifications from "./notifications";
|
||||||
import { contextifyComponent } from "./components";
|
import { contextifyComponent } from "./components";
|
||||||
import { PermissionSet, UiConfig } from "../lib/types";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
authorizer: Authorizer;
|
authorizer: Authorizer;
|
||||||
permissionSets: PermissionSet[];
|
permissionSets: any;
|
||||||
username: string;
|
username: string;
|
||||||
clientConfig: { ui: UiConfig };
|
clientConfig: any;
|
||||||
configSnapshot: string;
|
configSnapshot: string;
|
||||||
genieacsVersion: string;
|
genieacsVersion: string;
|
||||||
}
|
}
|
||||||
@@ -59,7 +58,7 @@ const adminPages = [
|
|||||||
"files",
|
"files",
|
||||||
"config",
|
"config",
|
||||||
"users",
|
"users",
|
||||||
"permissions",
|
"permissions"
|
||||||
];
|
];
|
||||||
|
|
||||||
let state;
|
let state;
|
||||||
@@ -71,7 +70,7 @@ let fulfillTimeout = null;
|
|||||||
function fulfill(): void {
|
function fulfill(): void {
|
||||||
clearTimeout(fulfillTimeout);
|
clearTimeout(fulfillTimeout);
|
||||||
fulfillTimeout = setTimeout(() => {
|
fulfillTimeout = setTimeout(() => {
|
||||||
store.fulfill(lastRenderTimestamp, pageVisitTimestamp, (updated) => {
|
store.fulfill(lastRenderTimestamp, pageVisitTimestamp).then(updated => {
|
||||||
if (updated) m.redraw();
|
if (updated) m.redraw();
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -89,7 +88,7 @@ function pagify(pageName, page): RouteResolver {
|
|||||||
attrs["page"] = pageName;
|
attrs["page"] = pageName;
|
||||||
return m(layout, attrs, p);
|
return m(layout, attrs, p);
|
||||||
},
|
},
|
||||||
onmatch: null,
|
onmatch: null
|
||||||
};
|
};
|
||||||
|
|
||||||
component.onmatch = (args, requestedPath) => {
|
component.onmatch = (args, requestedPath) => {
|
||||||
@@ -100,16 +99,16 @@ function pagify(pageName, page): RouteResolver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
page
|
page
|
||||||
.init(args)
|
.init(args)
|
||||||
.then((st) => {
|
.then(st => {
|
||||||
if (!st) return void m.route.set("/");
|
if (!st) return void m.route.set("/");
|
||||||
state = st;
|
state = st;
|
||||||
resolve();
|
resolve();
|
||||||
fulfill();
|
fulfill();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
if (!window.username && err.message.indexOf("authorized") >= 0) {
|
if (!window.username && err.message.indexOf("authorized") >= 0) {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
m.route.set("/login", { continue: requestedPath });
|
m.route.set("/login", { continue: requestedPath });
|
||||||
@@ -133,7 +132,7 @@ function redirectAdminPage(): RouteResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@@ -155,5 +154,5 @@ m.route(document.body, "/overview", {
|
|||||||
"/admin/files": pagify("files", filesPage),
|
"/admin/files": pagify("files", filesPage),
|
||||||
"/admin/config": pagify("config", configPage),
|
"/admin/config": pagify("config", configPage),
|
||||||
"/admin/users": pagify("users", usersPage),
|
"/admin/users": pagify("users", usersPage),
|
||||||
"/admin/permissions": pagify("permissions", permissionsPage),
|
"/admin/permissions": pagify("permissions", permissionsPage)
|
||||||
});
|
});
|
||||||
@@ -17,13 +17,11 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type AutocompleteCallback = (
|
|
||||||
value: string,
|
|
||||||
callback: (suggestions: { value: string; tip?: string }[]) => void
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
export default class Autocomplete {
|
export default class Autocomplete {
|
||||||
private callback: AutocompleteCallback;
|
private callback: (
|
||||||
|
value: string,
|
||||||
|
callback: (suggestions: { value: string; tip?: string }[]) => void
|
||||||
|
) => void;
|
||||||
private element: HTMLInputElement;
|
private element: HTMLInputElement;
|
||||||
private hideTimeout: NodeJS.Timeout;
|
private hideTimeout: NodeJS.Timeout;
|
||||||
private visible: boolean;
|
private visible: boolean;
|
||||||
@@ -31,7 +29,7 @@ export default class Autocomplete {
|
|||||||
private selection: number;
|
private selection: number;
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
|
|
||||||
public constructor(className: string, callback: AutocompleteCallback) {
|
public constructor(className, callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.element = null;
|
this.element = null;
|
||||||
this.hideTimeout = null;
|
this.hideTimeout = null;
|
||||||
@@ -46,7 +44,7 @@ export default class Autocomplete {
|
|||||||
this.container.className = className;
|
this.container.className = className;
|
||||||
}
|
}
|
||||||
|
|
||||||
public attach(el: HTMLInputElement): void {
|
public attach(el): void {
|
||||||
el.setAttribute("autocomplete", "off");
|
el.setAttribute("autocomplete", "off");
|
||||||
|
|
||||||
el.addEventListener("focus", () => {
|
el.addEventListener("focus", () => {
|
||||||
@@ -64,7 +62,7 @@ export default class Autocomplete {
|
|||||||
this.hide();
|
this.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener("keydown", (e) => {
|
el.addEventListener("keydown", e => {
|
||||||
if (this.element !== el) return;
|
if (this.element !== el) return;
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
if (this.visible) this.hide();
|
if (this.visible) this.hide();
|
||||||
@@ -110,7 +108,7 @@ export default class Autocomplete {
|
|||||||
private update(): void {
|
private update(): void {
|
||||||
const el = this.element;
|
const el = this.element;
|
||||||
|
|
||||||
this.callback(el.value, (suggestions) => {
|
this.callback(el.value, suggestions => {
|
||||||
if (this.element !== el) return;
|
if (this.element !== el) return;
|
||||||
this.default = null;
|
this.default = null;
|
||||||
|
|
||||||
@@ -156,7 +154,7 @@ export default class Autocomplete {
|
|||||||
|
|
||||||
const t = document.createTextNode(suggestion.value);
|
const t = document.createTextNode(suggestion.value);
|
||||||
e.appendChild(t);
|
e.appendChild(t);
|
||||||
e.addEventListener("mousedown", (ev) => {
|
e.addEventListener("mousedown", ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
el.value = suggestion.value;
|
el.value = suggestion.value;
|
||||||
if (this.element === el) this.update();
|
if (this.element === el) this.update();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { m } from "./components";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const onPasswordChange = vnode.attrs["onPasswordChange"];
|
const onPasswordChange = vnode.attrs["onPasswordChange"];
|
||||||
const enforceAuth = !vnode.attrs["noAuth"];
|
const enforceAuth = !vnode.attrs["noAuth"];
|
||||||
const username = vnode.attrs["username"];
|
const username = vnode.attrs["username"];
|
||||||
@@ -40,19 +40,19 @@ const component: ClosureComponent = (): Component => {
|
|||||||
type: "text",
|
type: "text",
|
||||||
value: vnode.state["username"],
|
value: vnode.state["username"],
|
||||||
disabled: !!username,
|
disabled: !!username,
|
||||||
oninput: (e) => {
|
oninput: e => {
|
||||||
vnode.state["username"] = e.target.value;
|
vnode.state["username"] = e.target.value;
|
||||||
},
|
},
|
||||||
oncreate: (_vnode) => {
|
oncreate: _vnode => {
|
||||||
(_vnode.dom as HTMLSelectElement).focus();
|
(_vnode.dom as HTMLSelectElement).focus();
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
let fields = {
|
let fields = {
|
||||||
newPassword: "New password",
|
newPassword: "New password",
|
||||||
confirmPassword: "Confirm password",
|
confirmPassword: "Confirm password"
|
||||||
};
|
};
|
||||||
if (enforceAuth)
|
if (enforceAuth)
|
||||||
fields = Object.assign({ authPassword: "Your password" }, fields);
|
fields = Object.assign({ authPassword: "Your password" }, fields);
|
||||||
@@ -66,9 +66,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
name: f,
|
name: f,
|
||||||
type: "password",
|
type: "password",
|
||||||
value: vnode.state[f],
|
value: vnode.state[f],
|
||||||
oninput: (e) => {
|
oninput: e => {
|
||||||
vnode.state[f] = e.target.value;
|
vnode.state[f] = e.target.value;
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -77,7 +77,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
const submit = m(
|
const submit = m(
|
||||||
"button.primary",
|
"button.primary",
|
||||||
{
|
{
|
||||||
type: "submit",
|
type: "submit"
|
||||||
},
|
},
|
||||||
"Change password"
|
"Change password"
|
||||||
) as VnodeDOM;
|
) as VnodeDOM;
|
||||||
@@ -89,7 +89,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
m(
|
m(
|
||||||
"form",
|
"form",
|
||||||
{
|
{
|
||||||
onsubmit: (e) => {
|
onsubmit: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (
|
if (
|
||||||
@@ -120,19 +120,19 @@ const component: ClosureComponent = (): Component => {
|
|||||||
if (onPasswordChange) onPasswordChange();
|
if (onPasswordChange) onPasswordChange();
|
||||||
(submit.dom as HTMLFormElement).disabled = false;
|
(submit.dom as HTMLFormElement).disabled = false;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
(submit.dom as HTMLFormElement).disabled = false;
|
(submit.dom as HTMLFormElement).disabled = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
form
|
form
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
return m("div.put-form", children);
|
return m("div.put-form", children);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ import { codeMirror } from "./dynamic-loader";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
return m("textarea", {
|
return m("textarea", {
|
||||||
name: vnode.attrs["id"],
|
name: vnode.attrs["id"],
|
||||||
value: vnode.attrs["value"],
|
value: vnode.attrs["value"],
|
||||||
oncreate: (_vnode) => {
|
oncreate: _vnode => {
|
||||||
const editor = codeMirror.fromTextArea(_vnode.dom, {
|
const editor = codeMirror.fromTextArea(_vnode.dom, {
|
||||||
mode: vnode.attrs["mode"],
|
mode: vnode.attrs["mode"],
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
@@ -40,21 +40,21 @@ const component: ClosureComponent = (): Component => {
|
|||||||
"Cmd-Enter": () => {
|
"Cmd-Enter": () => {
|
||||||
if (vnode.attrs["onSubmit"])
|
if (vnode.attrs["onSubmit"])
|
||||||
vnode.attrs["onSubmit"](_vnode.dom);
|
vnode.attrs["onSubmit"](_vnode.dom);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (vnode.attrs["onChange"]) {
|
if (vnode.attrs["onChange"]) {
|
||||||
editor.on("change", (e) => {
|
editor.on("change", e => {
|
||||||
vnode.attrs["onChange"](e.getValue());
|
vnode.attrs["onChange"](e.getValue());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vnode.attrs["focus"]) editor.focus();
|
if (vnode.attrs["focus"]) editor.focus();
|
||||||
if (vnode.attrs["onReady"]) vnode.attrs["onReady"](editor);
|
if (vnode.attrs["onReady"]) vnode.attrs["onReady"](editor);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -24,7 +24,7 @@ import m, {
|
|||||||
ComponentTypes,
|
ComponentTypes,
|
||||||
Lifecycle,
|
Lifecycle,
|
||||||
ClosureComponent,
|
ClosureComponent,
|
||||||
Vnode,
|
Vnode
|
||||||
} from "mithril";
|
} from "mithril";
|
||||||
import parameter from "./components/parameter";
|
import parameter from "./components/parameter";
|
||||||
import parameterList from "./components/parameter-list";
|
import parameterList from "./components/parameter-list";
|
||||||
@@ -53,7 +53,7 @@ const comps = {
|
|||||||
tags,
|
tags,
|
||||||
ping,
|
ping,
|
||||||
"device-link": deviceLink,
|
"device-link": deviceLink,
|
||||||
"long-text": longTextComponent,
|
"long-text": longTextComponent
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
||||||
@@ -96,7 +96,7 @@ const M = new Proxy(m, {
|
|||||||
get: (target, prop) => {
|
get: (target, prop) => {
|
||||||
if (prop === "context") return contextFn;
|
if (prop === "context") return contextFn;
|
||||||
else return Reflect.get(target, prop);
|
else return Reflect.get(target, prop);
|
||||||
},
|
}
|
||||||
}) as MC;
|
}) as MC;
|
||||||
|
|
||||||
function contextFn(context, ...argumentsList): Vnode {
|
function contextFn(context, ...argumentsList): Vnode {
|
||||||
@@ -125,17 +125,17 @@ export function contextifyComponent(component: ComponentTypes): ComponentTypes {
|
|||||||
if (typeof component !== "function") {
|
if (typeof component !== "function") {
|
||||||
c = Object.assign({}, component);
|
c = Object.assign({}, component);
|
||||||
const view = component.view;
|
const view = component.view;
|
||||||
c.view = function (vnode) {
|
c.view = function(vnode) {
|
||||||
const context = vnodeContext.get(vnode) || {};
|
const context = vnodeContext.get(vnode) || {};
|
||||||
const res = Reflect.apply(view, this, [vnode]);
|
const res = Reflect.apply(view, this, [vnode]);
|
||||||
applyContext(res, context);
|
applyContext(res, context);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
} else if (!component.prototype || !component.prototype.view) {
|
} else if (!component.prototype || !component.prototype.view) {
|
||||||
c = (initialNode) => {
|
c = initialNode => {
|
||||||
const state = (component as ClosureComponent)(initialNode);
|
const state = (component as ClosureComponent)(initialNode);
|
||||||
const view = state.view;
|
const view = state.view;
|
||||||
state.view = function (vnode) {
|
state.view = function(vnode) {
|
||||||
const context = vnodeContext.get(vnode) || {};
|
const context = vnodeContext.get(vnode) || {};
|
||||||
const res = Reflect.apply(view, this, [vnode]);
|
const res = Reflect.apply(view, this, [vnode]);
|
||||||
applyContext(res, context);
|
applyContext(res, context);
|
||||||
|
|||||||
+81
-121
@@ -30,168 +30,128 @@ function escapeRegExp(str): string {
|
|||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysByDepth: WeakMap<Record<string, unknown>, string[][]> = new WeakMap();
|
|
||||||
|
|
||||||
function orderKeysByDepth(device: Record<string, unknown>): string[][] {
|
|
||||||
if (keysByDepth.has(device)) return keysByDepth.get(device);
|
|
||||||
const res: string[][] = [];
|
|
||||||
for (const key of Object.keys(device)) {
|
|
||||||
let count = 0;
|
|
||||||
for (
|
|
||||||
let i = key.lastIndexOf(".", key.length - 2);
|
|
||||||
i >= 0;
|
|
||||||
i = key.lastIndexOf(".", i - 1)
|
|
||||||
)
|
|
||||||
++count;
|
|
||||||
while (res.length <= count) res.push([]);
|
|
||||||
res[count].push(key);
|
|
||||||
}
|
|
||||||
keysByDepth.set(device, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
const search = m("input", {
|
const search = m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
placeholder: "Search parameters",
|
placeholder: "Search parameters",
|
||||||
oninput: (e) => {
|
oninput: e => {
|
||||||
vnode.state["searchString"] = e.target.value;
|
vnode.state["searchString"] = e.target.value;
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
clearTimeout(vnode.state["timeout"]);
|
clearTimeout(vnode.state["timeout"]);
|
||||||
vnode.state["timeout"] = setTimeout(m.redraw, 500);
|
vnode.state["timeout"] = setTimeout(m.redraw, 500);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const instanceRegex = /\.[0-9]+$/;
|
const instanceRegex = /\.[0-9]+$/;
|
||||||
let re;
|
let re;
|
||||||
if (vnode.state["searchString"]) {
|
if (vnode.state["searchString"]) {
|
||||||
const keywords = vnode.state["searchString"]
|
const keywords = vnode.state["searchString"].split(" ").filter(s => s);
|
||||||
.split(" ")
|
|
||||||
.filter((s) => s);
|
|
||||||
if (keywords.length)
|
if (keywords.length)
|
||||||
re = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
|
re = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredKeys: string[] = [];
|
const rows = Object.keys(device)
|
||||||
const allKeys = orderKeysByDepth(device);
|
.sort()
|
||||||
let count = 0;
|
.map(k => {
|
||||||
for (const keys of allKeys) {
|
|
||||||
let c = 0;
|
|
||||||
for (const k of keys) {
|
|
||||||
const p = device[k];
|
const p = device[k];
|
||||||
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
const val = [];
|
||||||
if (re && !re.test(str)) continue;
|
const attrs = {};
|
||||||
++c;
|
if (re) {
|
||||||
if (count < 100) filteredKeys.push(k);
|
const str = p.value && p.value[0] ? `${k} ${p.value[0]}` : k;
|
||||||
}
|
if (!re.test(str)) attrs["style"] = "display: none;";
|
||||||
count += c;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
filteredKeys.sort();
|
if (p.object === false) {
|
||||||
|
val.push(
|
||||||
|
m(
|
||||||
|
"parameter",
|
||||||
|
Object.assign({ device: device, parameter: memoizedParse(k) })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (p.object && p.writable) {
|
||||||
|
if (instanceRegex.test(k)) {
|
||||||
|
val.push(
|
||||||
|
m(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
title: "Delete this instance",
|
||||||
|
onclick: () => {
|
||||||
|
taskQueue.queueTask({
|
||||||
|
name: "deleteObject",
|
||||||
|
device: device["DeviceID.ID"].value[0],
|
||||||
|
objectName: k
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getIcon("delete-instance")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
val.push(
|
||||||
|
m(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
title: "Create a new instance",
|
||||||
|
onclick: () => {
|
||||||
|
taskQueue.queueTask({
|
||||||
|
name: "addObject",
|
||||||
|
device: device["DeviceID.ID"].value[0],
|
||||||
|
objectName: k
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getIcon("add-instance")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rows = filteredKeys.map((k) => {
|
|
||||||
const p = device[k];
|
|
||||||
const val = [];
|
|
||||||
const attrs = { key: k };
|
|
||||||
|
|
||||||
if (p.object === false) {
|
|
||||||
val.push(
|
val.push(
|
||||||
m(
|
m(
|
||||||
"parameter",
|
"button",
|
||||||
Object.assign({ device: device, parameter: memoizedParse(k) })
|
{
|
||||||
|
title: "Refresh tree",
|
||||||
|
onclick: () => {
|
||||||
|
taskQueue.queueTask({
|
||||||
|
name: "getParameterValues",
|
||||||
|
device: device["DeviceID.ID"].value[0],
|
||||||
|
parameterNames: [k]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getIcon("refresh")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if (p.object && p.writable) {
|
|
||||||
if (instanceRegex.test(k)) {
|
|
||||||
val.push(
|
|
||||||
m(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
title: "Delete this instance",
|
|
||||||
onclick: () => {
|
|
||||||
taskQueue.queueTask({
|
|
||||||
name: "deleteObject",
|
|
||||||
device: device["DeviceID.ID"].value[0],
|
|
||||||
objectName: k,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getIcon("delete-instance")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
val.push(
|
|
||||||
m(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
title: "Create a new instance",
|
|
||||||
onclick: () => {
|
|
||||||
taskQueue.queueTask({
|
|
||||||
name: "addObject",
|
|
||||||
device: device["DeviceID.ID"].value[0],
|
|
||||||
objectName: k,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getIcon("add-instance")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val.push(
|
return m(
|
||||||
m(
|
"tr",
|
||||||
"button",
|
attrs,
|
||||||
{
|
m("td.left", m("long-text", { text: k })),
|
||||||
title: "Refresh tree",
|
m("td.right", val)
|
||||||
onclick: () => {
|
);
|
||||||
taskQueue.queueTask({
|
});
|
||||||
name: "getParameterValues",
|
|
||||||
device: device["DeviceID.ID"].value[0],
|
|
||||||
parameterNames: [k],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getIcon("refresh")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return m(
|
|
||||||
"tr",
|
|
||||||
attrs,
|
|
||||||
m("td.left", m("long-text", { text: k })),
|
|
||||||
m("td.right", val)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return m(
|
return m(
|
||||||
".all-parameters",
|
".all-parameters",
|
||||||
m(
|
m(
|
||||||
"a.download-csv",
|
"a.download-csv",
|
||||||
{
|
{
|
||||||
href: `api/devices/${encodeURIComponent(
|
href: `/api/devices/${encodeURIComponent(
|
||||||
device["DeviceID.ID"].value[0]
|
device["DeviceID.ID"].value[0]
|
||||||
)}.csv`,
|
)}.csv`,
|
||||||
download: "",
|
download: "",
|
||||||
style: "float: right;",
|
style: "float: right;"
|
||||||
},
|
},
|
||||||
"Download"
|
"Download"
|
||||||
),
|
),
|
||||||
search,
|
search,
|
||||||
m(
|
m(".parameter-list", m("table", m("tbody", rows)))
|
||||||
".parameter-list",
|
|
||||||
m("table", m("tbody", rows)),
|
|
||||||
m(
|
|
||||||
"m",
|
|
||||||
`Displaying ${filteredKeys.length} out of ${count} parameters.`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import * as store from "../store";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
if (vnode.attrs["filter"]) {
|
if (vnode.attrs["filter"]) {
|
||||||
if (
|
if (
|
||||||
!store.evaluateExpression(
|
!store.evaluateExpression(
|
||||||
@@ -34,13 +34,13 @@ const component: ClosureComponent = (): Component => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = Object.values(vnode.attrs["components"]).map((c) => {
|
const children = Object.values(vnode.attrs["components"]).map(c => {
|
||||||
if (typeof c !== "object") return `${c}`;
|
if (typeof c !== "object") return `${c}`;
|
||||||
return m(c["type"], c);
|
return m(c["type"], c);
|
||||||
});
|
});
|
||||||
if (vnode.attrs["element"]) return m(vnode.attrs["element"], children);
|
if (vnode.attrs["element"]) return m(vnode.attrs["element"], children);
|
||||||
else return children;
|
else return children;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import * as store from "../store";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
@@ -38,9 +38,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
onclick: () => {
|
onclick: () => {
|
||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "reboot",
|
name: "reboot",
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0]
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Reboot"
|
"Reboot"
|
||||||
)
|
)
|
||||||
@@ -54,9 +54,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
onclick: () => {
|
onclick: () => {
|
||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "factoryReset",
|
name: "factoryReset",
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0]
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Reset"
|
"Reset"
|
||||||
)
|
)
|
||||||
@@ -70,9 +70,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
onclick: () => {
|
onclick: () => {
|
||||||
taskQueue.stageDownload({
|
taskQueue.stageDownload({
|
||||||
name: "download",
|
name: "download",
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0]
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Push file"
|
"Push file"
|
||||||
)
|
)
|
||||||
@@ -93,17 +93,17 @@ const component: ClosureComponent = (): Component => {
|
|||||||
notifications.push("success", `${deviceId}: Device deleted`);
|
notifications.push("success", `${deviceId}: Device deleted`);
|
||||||
m.route.set("/devices");
|
m.route.set("/devices");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Delete"
|
"Delete"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return m(".actions-bar", buttons);
|
return m(".actions-bar", buttons);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ import { getIcon } from "../icons";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
const deviceId = device["DeviceID.ID"].value[0];
|
const deviceId = device["DeviceID.ID"].value[0];
|
||||||
const faults = store.fetch("faults", [
|
const faults = store.fetch("faults", [
|
||||||
"AND",
|
"AND",
|
||||||
[">", ["PARAM", "_id"], `${deviceId}:`],
|
[">", ["PARAM", "_id"], `${deviceId}:`],
|
||||||
["<", ["PARAM", "_id"], `${deviceId}:zzzz`],
|
["<", ["PARAM", "_id"], `${deviceId}:zzzz`]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
@@ -39,8 +39,8 @@ const component: ClosureComponent = (): Component => {
|
|||||||
"Code",
|
"Code",
|
||||||
"Message",
|
"Message",
|
||||||
"Retries",
|
"Retries",
|
||||||
"Timestamp",
|
"Timestamp"
|
||||||
].map((l) => m("th", l));
|
].map(l => m("th", l));
|
||||||
const thead = m("thead", m("tr", headers));
|
const thead = m("thead", m("tr", headers));
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -57,7 +57,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
title: "Delete fault",
|
title: "Delete fault",
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
store
|
store
|
||||||
.deleteResource("faults", f["_id"])
|
.deleteResource("faults", f["_id"])
|
||||||
@@ -66,24 +66,21 @@ const component: ClosureComponent = (): Component => {
|
|||||||
store.fulfill(Date.now(), Date.now());
|
store.fulfill(Date.now(), Date.now());
|
||||||
m.redraw();
|
m.redraw();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(Date.now(), Date.now());
|
store.fulfill(Date.now(), Date.now());
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("remove")
|
getIcon("remove")
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tbody;
|
let tbody;
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
tbody = m(
|
tbody = m("tbody", rows.map(r => m("tr", r)));
|
||||||
"tbody",
|
|
||||||
rows.map((r) => m("tr", r))
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
tbody = m(
|
tbody = m(
|
||||||
"tbody",
|
"tbody",
|
||||||
@@ -92,7 +89,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return m("table.table", thead, tbody);
|
return m("table.table", thead, tbody);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ import { m } from "../components";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
let deviceId;
|
let deviceId;
|
||||||
if (vnode.attrs["device"])
|
if (vnode.attrs["device"])
|
||||||
deviceId = vnode.attrs["device"]["DeviceID.ID"].value[0];
|
deviceId = vnode.attrs["device"]["DeviceID.ID"].value[0];
|
||||||
|
|
||||||
const children = Object.values(vnode.attrs["components"]).map((c) => {
|
const children = Object.values(vnode.attrs["components"]).map(c => {
|
||||||
if (typeof c !== "object") return `${c}`;
|
if (typeof c !== "object") return `${c}`;
|
||||||
const attrs = Object.assign({}, vnode.attrs, c);
|
const attrs = Object.assign({}, vnode.attrs, c);
|
||||||
return m(attrs["type"], attrs);
|
return m(attrs["type"], attrs);
|
||||||
@@ -41,7 +41,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
} else {
|
} else {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ const CHARTS = config.ui.overview.charts;
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
const chart = CHARTS[vnode.attrs["chart"]] as Record<string, unknown>;
|
const chart = CHARTS[vnode.attrs["chart"]];
|
||||||
if (!chart) return null;
|
if (!chart) return null;
|
||||||
for (const slice of Object.values(chart.slices)) {
|
for (const slice of Object.values(chart.slices)) {
|
||||||
const filter = slice["filter"];
|
const filter = slice["filter"];
|
||||||
@@ -39,20 +39,20 @@ const component: ClosureComponent = (): Component => {
|
|||||||
width: "1em",
|
width: "1em",
|
||||||
height: "1em",
|
height: "1em",
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||||
},
|
},
|
||||||
m("circle", {
|
m("circle", {
|
||||||
cx: "0.5em",
|
cx: "0.5em",
|
||||||
cy: "0.5em",
|
cy: "0.5em",
|
||||||
r: "0.4em",
|
r: "0.4em",
|
||||||
fill: slice["color"],
|
fill: slice["color"]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return m("span.overview-dot", dot, `${slice["label"]}`);
|
return m("span.overview-dot", dot, `${slice["label"]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ import { m } from "../components";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
const rows = Object.values(vnode.attrs["parameters"]).map((parameter) => {
|
const rows = Object.values(vnode.attrs["parameters"]).map(parameter => {
|
||||||
const p = m.context(
|
const p = m.context(
|
||||||
{
|
{
|
||||||
device: device,
|
device: device,
|
||||||
parameter: parameter["parameter"],
|
parameter: parameter["parameter"]
|
||||||
},
|
},
|
||||||
parameter["type"] || "parameter",
|
parameter["type"] || "parameter",
|
||||||
parameter
|
parameter
|
||||||
@@ -38,11 +38,11 @@ const component: ClosureComponent = (): Component => {
|
|||||||
return m(
|
return m(
|
||||||
"tr",
|
"tr",
|
||||||
{
|
{
|
||||||
onupdate: (vn) => {
|
onupdate: vn => {
|
||||||
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||||
? ""
|
? ""
|
||||||
: "none";
|
: "none";
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
m("th", parameter["label"]),
|
m("th", parameter["label"]),
|
||||||
m("td", p)
|
m("td", p)
|
||||||
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return m("table.parameter-list", rows);
|
return m("table.parameter-list", rows);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,21 +26,21 @@ import { getIcon } from "../icons";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: vnode => {
|
||||||
const obj = vnode.attrs["parameter"];
|
const obj = vnode.attrs["parameter"];
|
||||||
if (!Array.isArray(obj) || obj[0] !== "PARAM")
|
if (!Array.isArray(obj) || obj[0] !== "PARAM")
|
||||||
throw new Error("Object must be a parameter path");
|
throw new Error("Object must be a parameter path");
|
||||||
vnode.state["object"] = obj[1];
|
vnode.state["object"] = obj[1];
|
||||||
vnode.state["parameters"] = Object.values(vnode.attrs["childParameters"]);
|
vnode.state["parameters"] = Object.values(vnode.attrs["childParameters"]);
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
const object = store.evaluateExpression(vnode.state["object"], device);
|
const object = store.evaluateExpression(vnode.state["object"], device);
|
||||||
const parameters = vnode.state["parameters"];
|
const parameters = vnode.state["parameters"];
|
||||||
|
|
||||||
if (typeof object !== "string" || !device[object]) return null;
|
if (!device[object]) return null;
|
||||||
|
|
||||||
const instances: Set<string> = new Set();
|
const instances = new Set();
|
||||||
const prefix = `${object}.`;
|
const prefix = `${object}.`;
|
||||||
for (const p in device) {
|
for (const p in device) {
|
||||||
if (p.startsWith(prefix)) {
|
if (p.startsWith(prefix)) {
|
||||||
@@ -50,7 +50,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = Object.values(parameters).map((p) => m("th", p["label"]));
|
const headers = Object.values(parameters).map(p => m("th", p["label"]));
|
||||||
|
|
||||||
const thead = m("thead", m("tr", headers));
|
const thead = m("thead", m("tr", headers));
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
let filter =
|
let filter =
|
||||||
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
||||||
|
|
||||||
filter = expressionParser.map(filter, (e) => {
|
filter = expressionParser.map(filter, e => {
|
||||||
if (Array.isArray(e) && e[0] === "PARAM")
|
if (Array.isArray(e) && e[0] === "PARAM")
|
||||||
return ["PARAM", ["||", i, ".", e[1]]];
|
return ["PARAM", ["||", i, ".", e[1]]];
|
||||||
return e;
|
return e;
|
||||||
@@ -67,8 +67,8 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
if (!store.evaluateExpression(filter, device)) continue;
|
if (!store.evaluateExpression(filter, device)) continue;
|
||||||
|
|
||||||
const row = parameters.map((p) => {
|
const row = parameters.map(p => {
|
||||||
const param = expressionParser.map(p.parameter, (e) => {
|
const param = expressionParser.map(p.parameter, e => {
|
||||||
if (Array.isArray(e) && e[0] === "PARAM")
|
if (Array.isArray(e) && e[0] === "PARAM")
|
||||||
return ["PARAM", ["||", i, ".", e[1]]];
|
return ["PARAM", ["||", i, ".", e[1]]];
|
||||||
return e;
|
return e;
|
||||||
@@ -78,13 +78,13 @@ const component: ClosureComponent = (): Component => {
|
|||||||
m.context(
|
m.context(
|
||||||
{
|
{
|
||||||
device: device,
|
device: device,
|
||||||
parameter: param,
|
parameter: param
|
||||||
},
|
},
|
||||||
p.type || "parameter",
|
p.type || "parameter",
|
||||||
Object.assign({}, p, {
|
Object.assign({}, p, {
|
||||||
device: device,
|
device: device,
|
||||||
parameter: param,
|
parameter: param,
|
||||||
label: null,
|
label: null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -102,9 +102,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "deleteObject",
|
name: "deleteObject",
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0],
|
||||||
objectName: i,
|
objectName: i
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("delete-instance")
|
getIcon("delete-instance")
|
||||||
)
|
)
|
||||||
@@ -135,9 +135,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "addObject",
|
name: "addObject",
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0],
|
||||||
objectName: object,
|
objectName: object
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("add-instance")
|
getIcon("add-instance")
|
||||||
)
|
)
|
||||||
@@ -150,7 +150,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
||||||
|
|
||||||
return [label, m("table.table", thead, m("tbody", rows))];
|
return [label, m("table.table", thead, m("tbody", rows))];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+21
-39
@@ -28,49 +28,31 @@ import { getIcon } from "../icons";
|
|||||||
|
|
||||||
const evaluateParam = memoize((exp, obj, now: number) => {
|
const evaluateParam = memoize((exp, obj, now: number) => {
|
||||||
let timestamp = now;
|
let timestamp = now;
|
||||||
exp = expression.evaluate(exp, null, now, (e) => {
|
const params = new Set();
|
||||||
if (!Array.isArray(e)) return e;
|
const value = expression.evaluate(exp, obj, now, e => {
|
||||||
for (let i = 1; i < e.length; ++i) {
|
if (Array.isArray(e)) {
|
||||||
if (
|
if (e[0] === "PARAM") {
|
||||||
Array.isArray(e[i]) &&
|
params.add(e[1]);
|
||||||
e[i][0] === "PARAM" &&
|
if (!Array.isArray(e[1])) {
|
||||||
!Array.isArray(e[i][1])
|
const p = obj[e[1]];
|
||||||
) {
|
if (p && p.valueTimestamp)
|
||||||
let v = null;
|
timestamp = Math.min(timestamp, p.valueTimestamp);
|
||||||
const p = obj[e[i][1]];
|
|
||||||
if (p && p.value) {
|
|
||||||
v = p.value[0];
|
|
||||||
timestamp = Math.min(timestamp, p.valueTimestamp);
|
|
||||||
}
|
}
|
||||||
e = e.slice();
|
|
||||||
e[i] = v;
|
|
||||||
}
|
}
|
||||||
|
if (e[0] === "FUNC" && e[1] === "DATE_STRING" && !Array.isArray(e[2]))
|
||||||
|
return new Date(e[2]).toLocaleString();
|
||||||
}
|
}
|
||||||
if (e[0] === "FUNC" && e[1] === "DATE_STRING" && !Array.isArray(e[2]))
|
|
||||||
return new Date(e[2]).toLocaleString();
|
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|
||||||
let parameter = null;
|
let parameter = params.size === 1 ? params.values().next().value : null;
|
||||||
let value = null;
|
if (Array.isArray(parameter)) parameter = null;
|
||||||
|
|
||||||
if (!Array.isArray(exp)) {
|
|
||||||
value = exp;
|
|
||||||
} else if (exp[0] === "PARAM") {
|
|
||||||
const p = obj[exp[1]];
|
|
||||||
if (p && p.value) {
|
|
||||||
timestamp = p.valueTimestamp;
|
|
||||||
value = p.value[0];
|
|
||||||
parameter = exp[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { value, timestamp, parameter };
|
return { value, timestamp, parameter };
|
||||||
});
|
});
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
const { value, timestamp, parameter } = evaluateParam(
|
const { value, timestamp, parameter } = evaluateParam(
|
||||||
@@ -95,11 +77,11 @@ const component: ClosureComponent = (): Component => {
|
|||||||
[
|
[
|
||||||
parameter,
|
parameter,
|
||||||
device[parameter].value[0],
|
device[parameter].value[0],
|
||||||
device[parameter].value[1],
|
device[parameter].value[1]
|
||||||
],
|
]
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("edit")
|
getIcon("edit")
|
||||||
);
|
);
|
||||||
@@ -111,7 +93,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
"span",
|
"span",
|
||||||
{
|
{
|
||||||
class: "parameter-value",
|
class: "parameter-value",
|
||||||
onmouseover: (e) => {
|
onmouseover: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
// Don't update any child element
|
// Don't update any child element
|
||||||
if (e.target === (el as VnodeDOM).dom) {
|
if (e.target === (el as VnodeDOM).dom) {
|
||||||
@@ -119,12 +101,12 @@ const component: ClosureComponent = (): Component => {
|
|||||||
const localeString = new Date(timestamp).toLocaleString();
|
const localeString = new Date(timestamp).toLocaleString();
|
||||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
el,
|
el,
|
||||||
edit
|
edit
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+16
-21
@@ -27,35 +27,30 @@ const component: ClosureComponent = (vn): Component => {
|
|||||||
let interval: ReturnType<typeof setInterval>;
|
let interval: ReturnType<typeof setInterval>;
|
||||||
let host: string;
|
let host: string;
|
||||||
|
|
||||||
const refresh = (): void => {
|
const refresh = async (): Promise<void> => {
|
||||||
if (!host) {
|
|
||||||
const dom = (vn as VnodeDOM).dom;
|
|
||||||
if (dom) dom.innerHTML = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = "";
|
let status = "";
|
||||||
store
|
if (host) {
|
||||||
.ping(host)
|
try {
|
||||||
.then((res) => {
|
const res = await store.ping(host);
|
||||||
if (res["avg"] != null) status = `${Math.trunc(res["avg"])} ms`;
|
if (res["avg"] != null) status = `${Math.trunc(res["avg"])} ms`;
|
||||||
else status = "Unreachable";
|
else status = "Unreachable";
|
||||||
})
|
} catch (err) {
|
||||||
.catch(() => {
|
setTimeout(() => {
|
||||||
status = "Error!";
|
throw err;
|
||||||
clearInterval(interval);
|
}, 0);
|
||||||
})
|
}
|
||||||
.finally(() => {
|
}
|
||||||
const dom = (vn as VnodeDOM).dom;
|
|
||||||
if (dom) dom.innerHTML = `Pinging ${host}: ${status}`;
|
const dom = (vn as VnodeDOM).dom;
|
||||||
});
|
|
||||||
|
if (dom) dom.innerHTML = `Pinging ${host}: ${status}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onremove: () => {
|
onremove: () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
let param =
|
let param =
|
||||||
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"];
|
||||||
@@ -78,7 +73,7 @@ const component: ClosureComponent = (vn): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return m("div", `Pinging ${host}:`);
|
return m("div", `Pinging ${host}:`);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,26 +25,26 @@ import * as notifications from "../notifications";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
return m(
|
return m(
|
||||||
"button.primary",
|
"button.primary",
|
||||||
{
|
{
|
||||||
title: "Initiate session and refresh basic parameters",
|
title: "Initiate session and refresh basic parameters",
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
const params = Object.values(vnode.attrs["parameters"])
|
const params = Object.values(vnode.attrs["parameters"])
|
||||||
.map((exp) => {
|
.map(exp => {
|
||||||
if (!Array.isArray(exp) || exp[0] !== "PARAM") return null;
|
if (!Array.isArray(exp) || exp[0] !== "PARAM") return null;
|
||||||
return store.evaluateExpression(exp[1], device) as string;
|
return store.evaluateExpression(exp[1], device);
|
||||||
})
|
})
|
||||||
.filter((exp) => !!exp);
|
.filter(exp => !!exp);
|
||||||
|
|
||||||
const task = {
|
const task = {
|
||||||
name: "getParameterValues",
|
name: "getParameterValues",
|
||||||
parameterNames: params,
|
parameterNames: params,
|
||||||
device: device["DeviceID.ID"].value[0],
|
device: device["DeviceID.ID"].value[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
taskQueue
|
taskQueue
|
||||||
@@ -79,15 +79,12 @@ const component: ClosureComponent = (): Component => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Summon"
|
"Summon"
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-15
@@ -25,7 +25,7 @@ import { getIcon } from "../icons";
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
let writable = true;
|
let writable = true;
|
||||||
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
|
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
|
||||||
@@ -36,23 +36,18 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
tags.sort();
|
tags.sort();
|
||||||
|
|
||||||
if (!writable) {
|
if (!writable) return m(".tags", tags.map(t => m("span.tag", t)));
|
||||||
return m(
|
|
||||||
".tags",
|
|
||||||
tags.map((t) => m("span.tag", t))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return m(
|
return m(
|
||||||
".tags",
|
".tags",
|
||||||
tags.map((tag) =>
|
tags.map(tag =>
|
||||||
m(
|
m(
|
||||||
"span.tag",
|
"span.tag",
|
||||||
tag,
|
tag,
|
||||||
m(
|
m(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
const deviceId = device["DeviceID.ID"].value[0];
|
const deviceId = device["DeviceID.ID"].value[0];
|
||||||
store
|
store
|
||||||
@@ -65,14 +60,14 @@ const component: ClosureComponent = (): Component => {
|
|||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
notifications.push(
|
notifications.push(
|
||||||
"error",
|
"error",
|
||||||
`${deviceId}: ${err.message}`
|
`${deviceId}: ${err.message}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("remove")
|
getIcon("remove")
|
||||||
)
|
)
|
||||||
@@ -84,7 +79,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
m(
|
m(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
const deviceId = device["DeviceID.ID"].value[0];
|
const deviceId = device["DeviceID.ID"].value[0];
|
||||||
const tag = prompt(`Enter tag to assign to device:`);
|
const tag = prompt(`Enter tag to assign to device:`);
|
||||||
@@ -100,17 +95,17 @@ const component: ClosureComponent = (): Component => {
|
|||||||
notifications.push("success", `${deviceId}: Tags updated`);
|
notifications.push("success", `${deviceId}: Tags updated`);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("add")
|
getIcon("add")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface Diff {
|
|||||||
remove: string[];
|
remove: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenConfig(config: Record<string, unknown>): any {
|
export function flattenConfig(config): any {
|
||||||
const flatten = {};
|
const flatten = {};
|
||||||
const recuresive = (obj: any, root: string): void => {
|
const recuresive = (obj: any, root: string): void => {
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
@@ -56,7 +56,7 @@ export function structureConfig(config: Config[]): any {
|
|||||||
ref[keys[0]] = c.value;
|
ref[keys[0]] = c.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toArray = function (object): any {
|
const toArray = function(object): any {
|
||||||
const MAX_BITS = 30;
|
const MAX_BITS = 30;
|
||||||
const MAX_ARRAY_SIZE = MAX_BITS * 10;
|
const MAX_ARRAY_SIZE = MAX_BITS * 10;
|
||||||
|
|
||||||
@@ -96,13 +96,10 @@ export function structureConfig(config: Config[]): any {
|
|||||||
return toArray(_config);
|
return toArray(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function diffConfig(
|
export function diffConfig(current, target): Diff {
|
||||||
current: Record<string, unknown>,
|
|
||||||
target: Record<string, unknown>
|
|
||||||
): Diff {
|
|
||||||
const diff = {
|
const diff = {
|
||||||
add: [],
|
add: [],
|
||||||
remove: [],
|
remove: []
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(target))
|
for (const [k, v] of Object.entries(target))
|
||||||
|
|||||||
+30
-30
@@ -30,7 +30,7 @@ import { getIcon } from "./icons";
|
|||||||
|
|
||||||
const attributes = [
|
const attributes = [
|
||||||
{ id: "_id", label: "Key" },
|
{ id: "_id", label: "Key" },
|
||||||
{ id: "value", label: "Value", type: "textarea" },
|
{ id: "value", label: "Value", type: "textarea" }
|
||||||
];
|
];
|
||||||
|
|
||||||
interface ValidationErrors {
|
interface ValidationErrors {
|
||||||
@@ -52,13 +52,13 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
object.value = stringify(parse(object.value || ""));
|
object.value = stringify(parse(object.value || ""));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return void resolve({
|
return void resolve({
|
||||||
value: "Config value must be valid expression",
|
value: "Config value must be valid expression"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
store
|
store
|
||||||
.resourceExists("config", id)
|
.resourceExists("config", id)
|
||||||
.then((exists) => {
|
.then(exists => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
return void resolve({ _id: "Config already exists" });
|
return void resolve({ _id: "Config already exists" });
|
||||||
@@ -90,7 +90,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
@@ -102,14 +102,14 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
resource: "config",
|
resource: "config",
|
||||||
attributes: attributes,
|
attributes: attributes
|
||||||
};
|
};
|
||||||
|
|
||||||
function escapeRegExp(str): string {
|
function escapeRegExp(str): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init(): Promise<Record<string, unknown>> {
|
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")
|
||||||
@@ -131,9 +131,9 @@ function renderTable(confsResponse, searchString): Children {
|
|||||||
|
|
||||||
let regex;
|
let regex;
|
||||||
if (searchString) {
|
if (searchString) {
|
||||||
const keywords = searchString.split(" ").filter((s) => s);
|
const keywords = searchString.split(" ").filter(s => s);
|
||||||
if (keywords.length)
|
if (keywords.length)
|
||||||
regex = new RegExp(keywords.map((s) => escapeRegExp(s)).join(".*"), "i");
|
regex = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -154,9 +154,9 @@ function renderTable(confsResponse, searchString): Children {
|
|||||||
{
|
{
|
||||||
base: conf,
|
base: conf,
|
||||||
actionHandler: (action, object) => {
|
actionHandler: (action, object) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, false)
|
putActionHandler(action, object, false)
|
||||||
.then((errors) => {
|
.then(errors => {
|
||||||
const ErrorList = errors ? Object.values(errors) : [];
|
const ErrorList = errors ? Object.values(errors) : [];
|
||||||
if (ErrorList.length) {
|
if (ErrorList.length) {
|
||||||
for (const err of ErrorList)
|
for (const err of ErrorList)
|
||||||
@@ -166,19 +166,19 @@ function renderTable(confsResponse, searchString): Children {
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
formData
|
formData
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
overlay.open(cb);
|
overlay.open(cb);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("edit")
|
getIcon("edit")
|
||||||
);
|
);
|
||||||
@@ -190,10 +190,10 @@ function renderTable(confsResponse, searchString): Children {
|
|||||||
onclick: () => {
|
onclick: () => {
|
||||||
if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return;
|
if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return;
|
||||||
|
|
||||||
putActionHandler("delete", conf).catch((err) => {
|
putActionHandler("delete", conf).catch(err => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("remove")
|
getIcon("remove")
|
||||||
);
|
);
|
||||||
@@ -219,18 +219,18 @@ function renderTable(confsResponse, searchString): Children {
|
|||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
document.title = "Config - GenieACS";
|
document.title = "Config - GenieACS";
|
||||||
|
|
||||||
const search = m("input", {
|
const search = m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
placeholder: "Search config",
|
placeholder: "Search config",
|
||||||
oninput: (e) => {
|
oninput: e => {
|
||||||
vnode.state["searchString"] = e.target.value;
|
vnode.state["searchString"] = e.target.value;
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
clearTimeout(vnode.state["timeout"]);
|
clearTimeout(vnode.state["timeout"]);
|
||||||
vnode.state["timeout"] = setTimeout(m.redraw, 250);
|
vnode.state["timeout"] = setTimeout(m.redraw, 250);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const confs = store.fetch("config", true);
|
const confs = store.fetch("config", true);
|
||||||
@@ -250,9 +250,9 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
actionHandler: (action, object) => {
|
actionHandler: (action, object) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object, true)
|
putActionHandler(action, object, true)
|
||||||
.then((errors) => {
|
.then(errors => {
|
||||||
const errorList = errors
|
const errorList = errors
|
||||||
? Object.values(errors)
|
? Object.values(errors)
|
||||||
: [];
|
: [];
|
||||||
@@ -264,19 +264,19 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
formData
|
formData
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
overlay.open(cb);
|
overlay.open(cb);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"New config"
|
"New config"
|
||||||
);
|
);
|
||||||
@@ -286,7 +286,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
{ name: "charts", prefix: "ui.overview.charts.", data: [] },
|
{ name: "charts", prefix: "ui.overview.charts.", data: [] },
|
||||||
{ name: "filters", prefix: "ui.filters.", data: [] },
|
{ name: "filters", prefix: "ui.filters.", data: [] },
|
||||||
{ name: "index page", prefix: "ui.index.", data: [] },
|
{ name: "index page", prefix: "ui.index.", data: [] },
|
||||||
{ name: "device page", prefix: "ui.device.", data: [] },
|
{ name: "device page", prefix: "ui.device.", data: [] }
|
||||||
];
|
];
|
||||||
|
|
||||||
if (confs.fulfilled) {
|
if (confs.fulfilled) {
|
||||||
@@ -312,7 +312,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
uiConfigComponent,
|
uiConfigComponent,
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
onUpdate: (errs: string[]) => {
|
onUpdate: errs => {
|
||||||
const errors = errs ? Object.values(errs) : [];
|
const errors = errs ? Object.values(errs) : [];
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
for (const err of errors)
|
for (const err of errors)
|
||||||
@@ -329,18 +329,18 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
attrs
|
attrs
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
overlay.open(cb);
|
overlay.open(cb);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
`Edit ${sub.name}`
|
`Edit ${sub.name}`
|
||||||
)
|
)
|
||||||
@@ -359,8 +359,8 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
renderTable(confs, vnode.state["searchString"])
|
renderTable(confs, vnode.state["searchString"])
|
||||||
),
|
),
|
||||||
m(".actions-bar", [newConfig].concat(subs))
|
m(".actions-bar", [newConfig].concat(subs))
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+2
-2
@@ -460,7 +460,7 @@ input {
|
|||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
& > input {
|
& > input {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
width: 35em;
|
width: 30em;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
@@ -562,7 +562,7 @@ span.na {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
width: 700px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .parameter-list {
|
& > .parameter-list {
|
||||||
|
|||||||
+4
-6
@@ -22,9 +22,7 @@ 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(
|
export function init(args): Promise<{}> {
|
||||||
args: Record<string, unknown>
|
|
||||||
): Promise<Record<string, unknown>> {
|
|
||||||
if (!window.authorizer.hasAccess("devices", 2)) {
|
if (!window.authorizer.hasAccess("devices", 2)) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("You are not authorized to view this page")
|
new Error("You are not authorized to view this page")
|
||||||
@@ -33,13 +31,13 @@ export function init(
|
|||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
deviceId: args.id,
|
deviceId: args.id,
|
||||||
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id],
|
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
||||||
|
|
||||||
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
|
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
|
||||||
@@ -51,6 +49,6 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
cmps.push(m.context({ device: dev[0] }, c["type"], c));
|
cmps.push(m.context({ device: dev[0] }, c["type"], c));
|
||||||
|
|
||||||
return [m("h1", vnode.attrs["deviceId"]), cmps];
|
return [m("h1", vnode.attrs["deviceId"]), cmps];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+23
-25
@@ -35,7 +35,7 @@ const PAGE_SIZE = config.ui.pageSize || 10;
|
|||||||
|
|
||||||
const memoizedParse = memoize(parse);
|
const memoizedParse = memoize(parse);
|
||||||
const memoizedJsonParse = memoize(JSON.parse);
|
const memoizedJsonParse = memoize(JSON.parse);
|
||||||
const memoizedGetSortable = memoize((p) => {
|
const memoizedGetSortable = memoize(p => {
|
||||||
const expressionParams = extractParams(p);
|
const expressionParams = extractParams(p);
|
||||||
if (expressionParams.length === 1) {
|
if (expressionParams.length === 1) {
|
||||||
const param = evaluate(expressionParams[0]);
|
const param = evaluate(expressionParams[0]);
|
||||||
@@ -47,23 +47,21 @@ const memoizedGetSortable = memoize((p) => {
|
|||||||
const getDownloadUrl = memoize((filter, indexParameters) => {
|
const getDownloadUrl = memoize((filter, indexParameters) => {
|
||||||
const columns = {};
|
const columns = {};
|
||||||
for (const p of indexParameters) columns[p.label] = stringify(p.parameter);
|
for (const p of indexParameters) columns[p.label] = stringify(p.parameter);
|
||||||
return `api/devices.csv?${m.buildQueryString({
|
return `/api/devices.csv?${m.buildQueryString({
|
||||||
filter: stringify(filter),
|
filter: stringify(filter),
|
||||||
columns: JSON.stringify(columns),
|
columns: JSON.stringify(columns)
|
||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const unpackSmartQuery = memoize((query) => {
|
const unpackSmartQuery = memoize(query => {
|
||||||
return expressionParser.map(query, (e) => {
|
return expressionParser.map(query, e => {
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
return smartQuery.unpack("devices", e[2], e[3]);
|
return smartQuery.unpack("devices", e[2], e[3]);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(
|
export function init(args): Promise<{}> {
|
||||||
args: Record<string, unknown>
|
|
||||||
): Promise<Record<string, unknown>> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!window.authorizer.hasAccess("devices", 2))
|
if (!window.authorizer.hasAccess("devices", 2))
|
||||||
return void reject(new Error("You are not authorized to view this page"));
|
return void reject(new Error("You are not authorized to view this page"));
|
||||||
@@ -74,14 +72,14 @@ export function init(
|
|||||||
if (!indexParameters.length) {
|
if (!indexParameters.length) {
|
||||||
indexParameters.push({
|
indexParameters.push({
|
||||||
label: "ID",
|
label: "ID",
|
||||||
parameter: ["PARAM", "DeviceID.ID"],
|
parameter: ["PARAM", "DeviceID.ID"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resolve({ filter, indexParameters, sort });
|
resolve({ filter, indexParameters, sort });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderActions(selected: Set<string>): Children {
|
function renderActions(selected): Children {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
@@ -94,10 +92,10 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
for (const d of selected) {
|
for (const d of selected) {
|
||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "reboot",
|
name: "reboot",
|
||||||
device: d,
|
device: d
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Reboot"
|
"Reboot"
|
||||||
)
|
)
|
||||||
@@ -113,10 +111,10 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
for (const d of selected) {
|
for (const d of selected) {
|
||||||
taskQueue.queueTask({
|
taskQueue.queueTask({
|
||||||
name: "factoryReset",
|
name: "factoryReset",
|
||||||
device: d,
|
device: d
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Reset"
|
"Reset"
|
||||||
)
|
)
|
||||||
@@ -141,13 +139,13 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
notifications.push("success", `${id}: Deleted`);
|
notifications.push("success", `${id}: Deleted`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Delete"
|
"Delete"
|
||||||
)
|
)
|
||||||
@@ -173,13 +171,13 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
notifications.push("success", `${id}: Tags updated`);
|
notifications.push("success", `${id}: Tags updated`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Tag"
|
"Tag"
|
||||||
)
|
)
|
||||||
@@ -207,13 +205,13 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
notifications.push("success", `${id}: Tags updated`);
|
notifications.push("success", `${id}: Tags updated`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.fulfill(0, Date.now());
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Untag"
|
"Untag"
|
||||||
)
|
)
|
||||||
@@ -224,7 +222,7 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
document.title = "Devices - GenieACS";
|
document.title = "Devices - GenieACS";
|
||||||
const attributes = vnode.attrs["indexParameters"];
|
const attributes = vnode.attrs["indexParameters"];
|
||||||
|
|
||||||
@@ -275,7 +273,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const devs = store.fetch("devices", filter, {
|
const devs = store.fetch("devices", filter, {
|
||||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||||
sort: sort,
|
sort: sort
|
||||||
});
|
});
|
||||||
const count = store.count("devices", filter);
|
const count = store.count("devices", filter);
|
||||||
|
|
||||||
@@ -304,7 +302,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
{
|
{
|
||||||
href: `#!/devices/${encodeURIComponent(
|
href: `#!/devices/${encodeURIComponent(
|
||||||
device["DeviceID.ID"].value[0]
|
device["DeviceID.ID"].value[0]
|
||||||
)}`,
|
)}`
|
||||||
},
|
},
|
||||||
"Show"
|
"Show"
|
||||||
);
|
);
|
||||||
@@ -321,8 +319,8 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing devices"),
|
m("h1", "Listing devices"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(indexTableComponent, attrs)
|
||||||
];
|
];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+36
-39
@@ -22,7 +22,7 @@ import m, {
|
|||||||
Child,
|
Child,
|
||||||
ClosureComponent,
|
ClosureComponent,
|
||||||
Component,
|
Component,
|
||||||
VnodeDOM,
|
VnodeDOM
|
||||||
} from "mithril";
|
} from "mithril";
|
||||||
import * as taskQueue from "./task-queue";
|
import * as taskQueue from "./task-queue";
|
||||||
import * as store from "./store";
|
import * as store from "./store";
|
||||||
@@ -50,18 +50,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
|
|||||||
"select",
|
"select",
|
||||||
{
|
{
|
||||||
value: task.parameterValues[0][1].toString(),
|
value: task.parameterValues[0][1].toString(),
|
||||||
onchange: (e) => {
|
onchange: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
task.parameterValues[0][1] = input.dom.value;
|
task.parameterValues[0][1] = input.dom.value;
|
||||||
},
|
},
|
||||||
onkeydown: keydown,
|
onkeydown: keydown,
|
||||||
oncreate: (vnode) => {
|
oncreate: vnode => {
|
||||||
(vnode.dom as HTMLSelectElement).focus();
|
(vnode.dom as HTMLSelectElement).focus();
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
m("option", { value: "true" }, "true"),
|
m("option", { value: "true" }, "true"),
|
||||||
m("option", { value: "false" }, "false"),
|
m("option", { value: "false" }, "false")
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -72,18 +72,18 @@ function renderStagingSpv(task, queueFunc, cancelFunc): Children {
|
|||||||
? "number"
|
? "number"
|
||||||
: "text",
|
: "text",
|
||||||
value: task.parameterValues[0][1],
|
value: task.parameterValues[0][1],
|
||||||
oninput: (e) => {
|
oninput: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
task.parameterValues[0][1] = input.dom.value;
|
task.parameterValues[0][1] = input.dom.value;
|
||||||
},
|
},
|
||||||
onkeydown: keydown,
|
onkeydown: keydown,
|
||||||
oncreate: (vnode) => {
|
oncreate: vnode => {
|
||||||
(vnode.dom as HTMLInputElement).focus();
|
(vnode.dom as HTMLInputElement).focus();
|
||||||
(vnode.dom as HTMLInputElement).select();
|
(vnode.dom as HTMLInputElement).select();
|
||||||
// Need to prevent scrolling on focus because
|
// Need to prevent scrolling on focus because
|
||||||
// we're animating height and using overflow: hidden
|
// we're animating height and using overflow: hidden
|
||||||
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
|
(vnode.dom.parentNode.parentNode as Element).scrollTop = 0;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +104,8 @@ function renderStagingDownload(task): Children {
|
|||||||
"2 Web Content",
|
"2 Web Content",
|
||||||
"3 Vendor Configuration File",
|
"3 Vendor Configuration File",
|
||||||
"4 Tone File",
|
"4 Tone File",
|
||||||
"5 Ringer File",
|
"5 Ringer File"
|
||||||
].map((t) =>
|
].map(t =>
|
||||||
m(
|
m(
|
||||||
"option",
|
"option",
|
||||||
{ disabled: !t, value: t, selected: (task.fileType || "") === t },
|
{ disabled: !t, value: t, selected: (task.fileType || "") === t },
|
||||||
@@ -117,14 +117,14 @@ function renderStagingDownload(task): Children {
|
|||||||
.concat(
|
.concat(
|
||||||
files.value
|
files.value
|
||||||
.filter(
|
.filter(
|
||||||
(f) =>
|
f =>
|
||||||
(!f["metadata.oui"] || f["metadata.oui"] === oui) &&
|
(!f["metadata.oui"] || f["metadata.oui"] === oui) &&
|
||||||
(!f["metadata.productClass"] ||
|
(!f["metadata.productClass"] ||
|
||||||
f["metadata.productClass"] === productClass)
|
f["metadata.productClass"] === productClass)
|
||||||
)
|
)
|
||||||
.map((f) => f._id)
|
.map(f => f._id)
|
||||||
)
|
)
|
||||||
.map((f) =>
|
.map(f =>
|
||||||
m(
|
m(
|
||||||
"option",
|
"option",
|
||||||
{ disabled: !f, value: f, selected: (task.fileName || "") === f },
|
{ disabled: !f, value: f, selected: (task.fileName || "") === f },
|
||||||
@@ -137,7 +137,7 @@ function renderStagingDownload(task): Children {
|
|||||||
m(
|
m(
|
||||||
"select",
|
"select",
|
||||||
{
|
{
|
||||||
onchange: (e) => {
|
onchange: e => {
|
||||||
const f = e.target.value;
|
const f = e.target.value;
|
||||||
task.fileName = f;
|
task.fileName = f;
|
||||||
task.fileType = "";
|
task.fileType = "";
|
||||||
@@ -145,7 +145,7 @@ function renderStagingDownload(task): Children {
|
|||||||
if (file._id === f) task.fileType = file["metadata.fileType"];
|
if (file._id === f) task.fileType = file["metadata.fileType"];
|
||||||
},
|
},
|
||||||
disabled: files.fulfilling,
|
disabled: files.fulfilling,
|
||||||
style: "width: 350px",
|
style: "width: 350px"
|
||||||
},
|
},
|
||||||
filesList
|
filesList
|
||||||
),
|
),
|
||||||
@@ -153,12 +153,12 @@ function renderStagingDownload(task): Children {
|
|||||||
m(
|
m(
|
||||||
"select",
|
"select",
|
||||||
{
|
{
|
||||||
onchange: (e) => {
|
onchange: e => {
|
||||||
task.fileType = e.target.value;
|
task.fileType = e.target.value;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
typesList
|
typesList
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ function renderQueue(queue): Child[] {
|
|||||||
title: "Retry this task",
|
title: "Retry this task",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
taskQueue.queueTask(t);
|
taskQueue.queueTask(t);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("retry")
|
getIcon("retry")
|
||||||
)
|
)
|
||||||
@@ -230,7 +230,7 @@ function renderQueue(queue): Child[] {
|
|||||||
title: "Remove this task",
|
title: "Remove this task",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
taskQueue.deleteTask(t);
|
taskQueue.deleteTask(t);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
getIcon("remove")
|
getIcon("remove")
|
||||||
)
|
)
|
||||||
@@ -324,18 +324,18 @@ function renderNotifications(notifs): Child[] {
|
|||||||
{
|
{
|
||||||
class: n.type,
|
class: n.type,
|
||||||
style: "position: absolute;opacity: 0",
|
style: "position: absolute;opacity: 0",
|
||||||
oncreate: (vnode) => {
|
oncreate: vnode => {
|
||||||
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
(vnode.dom as HTMLDivElement).style.opacity = "1";
|
||||||
},
|
},
|
||||||
onbeforeremove: (vnode) => {
|
onbeforeremove: vnode => {
|
||||||
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
(vnode.dom as HTMLDivElement).style.opacity = "0";
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve();
|
resolve();
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
key: n.timestamp,
|
key: n.timestamp
|
||||||
},
|
},
|
||||||
m("div", buttons, n.message)
|
m("div", buttons, n.message)
|
||||||
)
|
)
|
||||||
@@ -346,7 +346,7 @@ function renderNotifications(notifs): Child[] {
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
const queue = taskQueue.getQueue();
|
const queue = taskQueue.getQueue();
|
||||||
const staging = taskQueue.getStaging();
|
const staging = taskQueue.getStaging();
|
||||||
const notifs = notifications.getNotifications();
|
const notifs = notifications.getNotifications();
|
||||||
@@ -395,7 +395,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
disabled: !statusCount.queued,
|
disabled: !statusCount.queued,
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const tasks = Array.from(taskQueue.getQueue()).filter(
|
const tasks = Array.from(taskQueue.getQueue()).filter(
|
||||||
(t) => t["status"] === "queued"
|
t => t["status"] === "queued"
|
||||||
);
|
);
|
||||||
taskQueue
|
taskQueue
|
||||||
.commit(
|
.commit(
|
||||||
@@ -441,11 +441,8 @@ const component: ClosureComponent = (): Component => {
|
|||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Commit"
|
"Commit"
|
||||||
),
|
),
|
||||||
@@ -454,7 +451,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
{
|
{
|
||||||
title: "Clear tasks",
|
title: "Clear tasks",
|
||||||
onclick: taskQueue.clear,
|
onclick: taskQueue.clear,
|
||||||
disabled: !queueElements.length,
|
disabled: !queueElements.length
|
||||||
},
|
},
|
||||||
"Clear"
|
"Clear"
|
||||||
)
|
)
|
||||||
@@ -490,31 +487,31 @@ const component: ClosureComponent = (): Component => {
|
|||||||
{
|
{
|
||||||
key: "drawer",
|
key: "drawer",
|
||||||
style: "opacity: 0;height: 0;",
|
style: "opacity: 0;height: 0;",
|
||||||
oncreate: (vnode2) => {
|
oncreate: vnode2 => {
|
||||||
vnode.state["mouseIn"] = false;
|
vnode.state["mouseIn"] = false;
|
||||||
(vnode2.dom as HTMLDivElement).style.opacity = "1";
|
(vnode2.dom as HTMLDivElement).style.opacity = "1";
|
||||||
resizeDrawer();
|
resizeDrawer();
|
||||||
},
|
},
|
||||||
onmouseover: (e) => {
|
onmouseover: e => {
|
||||||
vnode.state["mouseIn"] = true;
|
vnode.state["mouseIn"] = true;
|
||||||
resizeDrawer();
|
resizeDrawer();
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
},
|
},
|
||||||
onmouseleave: (e) => {
|
onmouseleave: e => {
|
||||||
vnode.state["mouseIn"] = false;
|
vnode.state["mouseIn"] = false;
|
||||||
resizeDrawer();
|
resizeDrawer();
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
},
|
},
|
||||||
onupdate: resizeDrawer,
|
onupdate: resizeDrawer,
|
||||||
onbeforeremove: (vnode2) => {
|
onbeforeremove: vnode2 => {
|
||||||
(vnode2.dom as HTMLDivElement).onmouseover = null;
|
(vnode2.dom as HTMLDivElement).onmouseover = null;
|
||||||
(vnode2.dom as HTMLDivElement).onmouseleave = null;
|
(vnode2.dom as HTMLDivElement).onmouseleave = null;
|
||||||
(vnode2.dom as HTMLDivElement).style.opacity = "0";
|
(vnode2.dom as HTMLDivElement).style.opacity = "0";
|
||||||
(vnode2.dom as HTMLDivElement).style.height = "0";
|
(vnode2.dom as HTMLDivElement).style.height = "0";
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
statusElement,
|
statusElement,
|
||||||
stagingElements.length ? stagingElements : m(".queue", queueElements)
|
stagingElements.length ? stagingElements : m(".queue", queueElements)
|
||||||
@@ -530,12 +527,12 @@ const component: ClosureComponent = (): Component => {
|
|||||||
key: "notifications",
|
key: "notifications",
|
||||||
style: "position: relative;",
|
style: "position: relative;",
|
||||||
onupdate: repositionNotifications,
|
onupdate: repositionNotifications,
|
||||||
oncreate: repositionNotifications,
|
oncreate: repositionNotifications
|
||||||
},
|
},
|
||||||
notificationElements
|
notificationElements
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function onError(): void {
|
|||||||
{
|
{
|
||||||
Reload: () => {
|
Reload: () => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -47,14 +47,14 @@ export function loadCodeMirror(): Promise<void> {
|
|||||||
import(
|
import(
|
||||||
/* webpackChunkName: "codemirror" */ "codemirror/mode/javascript/javascript"
|
/* webpackChunkName: "codemirror" */ "codemirror/mode/javascript/javascript"
|
||||||
),
|
),
|
||||||
import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml"),
|
import(/* webpackChunkName: "codemirror" */ "codemirror/mode/yaml/yaml")
|
||||||
];
|
];
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then((modules) => {
|
.then(modules => {
|
||||||
codeMirror = modules[0];
|
codeMirror = modules[0];
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
onError();
|
onError();
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
@@ -66,11 +66,11 @@ export function loadYaml(): Promise<void> {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
import(/* webpackChunkName: "yaml" */ "yaml")
|
import(/* webpackChunkName: "yaml" */ "yaml")
|
||||||
.then((module) => {
|
.then(module => {
|
||||||
yaml = module;
|
yaml = module;
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
onError();
|
onError();
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -22,9 +22,9 @@ import { m } from "./components";
|
|||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: function (vnode) {
|
view: function(vnode) {
|
||||||
document.title = "Error! - GenieACS";
|
document.title = "Error! - GenieACS";
|
||||||
return m("p.error", vnode.attrs["error"]);
|
return m("p.error", vnode.attrs["error"]);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+17
-19
@@ -41,33 +41,31 @@ const attributes = [
|
|||||||
{ id: "message", label: "Message" },
|
{ id: "message", label: "Message" },
|
||||||
{ id: "detail", label: "Detail" },
|
{ id: "detail", label: "Detail" },
|
||||||
{ id: "retries", label: "Retries" },
|
{ id: "retries", label: "Retries" },
|
||||||
{ id: "timestamp", label: "Timestamp" },
|
{ id: "timestamp", label: "Timestamp" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const getDownloadUrl = memoize((filter) => {
|
const getDownloadUrl = memoize(filter => {
|
||||||
const cols = {};
|
const cols = {};
|
||||||
for (const attr of attributes) {
|
for (const attr of attributes) {
|
||||||
cols[attr.label] =
|
cols[attr.label] =
|
||||||
attr.id === "timestamp" ? `DATE_STRING(${attr.id})` : attr.id;
|
attr.id === "timestamp" ? `DATE_STRING(${attr.id})` : attr.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `api/faults.csv?${m.buildQueryString({
|
return `/api/faults.csv?${m.buildQueryString({
|
||||||
filter: stringify(filter),
|
filter: stringify(filter),
|
||||||
columns: JSON.stringify(cols),
|
columns: JSON.stringify(cols)
|
||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const unpackSmartQuery = memoize((query) => {
|
const unpackSmartQuery = memoize(query => {
|
||||||
return map(query, (e) => {
|
return map(query, e => {
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
return smartQuery.unpack("faults", e[2], e[3]);
|
return smartQuery.unpack("faults", e[2], e[3]);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(
|
export function init(args): Promise<{}> {
|
||||||
args: Record<string, unknown>
|
|
||||||
): Promise<Record<string, unknown>> {
|
|
||||||
if (!window.authorizer.hasAccess("faults", 2)) {
|
if (!window.authorizer.hasAccess("faults", 2)) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("You are not authorized to view this page")
|
new Error("You are not authorized to view this page")
|
||||||
@@ -87,7 +85,7 @@ export function init(
|
|||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
document.title = "Faults - GenieACS";
|
document.title = "Faults - GenieACS";
|
||||||
|
|
||||||
function showMore(): void {
|
function showMore(): void {
|
||||||
@@ -132,7 +130,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const faults = store.fetch("faults", filter, {
|
const faults = store.fetch("faults", filter, {
|
||||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||||
sort: sort,
|
sort: sort
|
||||||
});
|
});
|
||||||
const count = store.count("faults", filter);
|
const count = store.count("faults", filter);
|
||||||
|
|
||||||
@@ -167,13 +165,13 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
attrs["downloadUrl"] = downloadUrl;
|
attrs["downloadUrl"] = downloadUrl;
|
||||||
|
|
||||||
if (window.authorizer.hasAccess("faults", 3)) {
|
if (window.authorizer.hasAccess("faults", 3)) {
|
||||||
attrs["actionsCallback"] = (selected: Set<string>): Children => {
|
attrs["actionsCallback"] = (selected): Children => {
|
||||||
return m(
|
return m(
|
||||||
"button.primary",
|
"button.primary",
|
||||||
{
|
{
|
||||||
disabled: selected.size === 0,
|
disabled: selected.size === 0,
|
||||||
title: "Delete selected faults",
|
title: "Delete selected faults",
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
|
|
||||||
@@ -181,22 +179,22 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
Array.from(selected).map((id) =>
|
Array.from(selected).map(id =>
|
||||||
store.deleteResource("faults", id)
|
store.deleteResource("faults", id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
notifications.push(
|
notifications.push(
|
||||||
"success",
|
"success",
|
||||||
`${res.length} faults deleted`
|
`${res.length} faults deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Delete"
|
"Delete"
|
||||||
);
|
);
|
||||||
@@ -211,8 +209,8 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing faults"),
|
m("h1", "Listing faults"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(indexTableComponent, attrs)
|
||||||
];
|
];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+31
-33
@@ -51,23 +51,23 @@ const attributes: {
|
|||||||
"2 Web Content",
|
"2 Web Content",
|
||||||
"3 Vendor Configuration File",
|
"3 Vendor Configuration File",
|
||||||
"4 Tone File",
|
"4 Tone File",
|
||||||
"5 Ringer File",
|
"5 Ringer File"
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{ id: "metadata.oui", label: "OUI" },
|
{ id: "metadata.oui", label: "OUI" },
|
||||||
{ id: "metadata.productClass", label: "Product Class" },
|
{ id: "metadata.productClass", label: "Product Class" },
|
||||||
{ id: "metadata.version", label: "Version" },
|
{ id: "metadata.version", label: "Version" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
resource: "files",
|
resource: "files",
|
||||||
attributes: attributes
|
attributes: attributes
|
||||||
.slice(1) // remove _id from new object form
|
.slice(1) // remove _id from new object form
|
||||||
.concat([{ id: "file", label: "File", type: "file" }]),
|
.concat([{ id: "file", label: "File", type: "file" }])
|
||||||
};
|
};
|
||||||
|
|
||||||
const unpackSmartQuery = memoize((query) => {
|
const unpackSmartQuery = memoize(query => {
|
||||||
return map(query, (e) => {
|
return map(query, e => {
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
return smartQuery.unpack("files", e[2], e[3]);
|
return smartQuery.unpack("files", e[2], e[3]);
|
||||||
return e;
|
return e;
|
||||||
@@ -91,7 +91,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
|
|
||||||
store
|
store
|
||||||
.resourceExists("files", id)
|
.resourceExists("files", id)
|
||||||
.then((exists) => {
|
.then(exists => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
return void resolve({ file: "File already exists" });
|
return void resolve({ file: "File already exists" });
|
||||||
@@ -99,7 +99,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
const headers = Object.assign(
|
const headers = Object.assign(
|
||||||
{
|
{
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
Accept: "application/octet-stream",
|
Accept: "application/octet-stream"
|
||||||
},
|
},
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
@@ -108,9 +108,9 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
.xhrRequest({
|
.xhrRequest({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: headers,
|
headers: headers,
|
||||||
url: `api/files/${encodeURIComponent(id)}`,
|
url: `/api/files/${encodeURIComponent(id)}`,
|
||||||
serialize: (body) => body, // Identity function to prevent JSON.parse on blob data
|
serialize: body => body, // Identity function to prevent JSON.parse on blob data
|
||||||
body: file,
|
body: file
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push(
|
notifications.push(
|
||||||
@@ -129,18 +129,16 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDownloadUrl = memoize((filter) => {
|
const getDownloadUrl = memoize(filter => {
|
||||||
const cols = {};
|
const cols = {};
|
||||||
for (const attr of attributes) cols[attr.label] = attr.id;
|
for (const attr of attributes) cols[attr.label] = attr.id;
|
||||||
return `api/files.csv?${m.buildQueryString({
|
return `/api/files.csv?${m.buildQueryString({
|
||||||
filter: stringify(filter),
|
filter: stringify(filter),
|
||||||
columns: JSON.stringify(cols),
|
columns: JSON.stringify(cols)
|
||||||
})}`;
|
})}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function init(
|
export function init(args): Promise<{}> {
|
||||||
args: Record<string, unknown>
|
|
||||||
): Promise<Record<string, unknown>> {
|
|
||||||
if (!window.authorizer.hasAccess("files", 2)) {
|
if (!window.authorizer.hasAccess("files", 2)) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("You are not authorized to view this page")
|
new Error("You are not authorized to view this page")
|
||||||
@@ -154,7 +152,7 @@ export function init(
|
|||||||
|
|
||||||
export const component: ClosureComponent = (): Component => {
|
export const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
document.title = "Files - GenieACS";
|
document.title = "Files - GenieACS";
|
||||||
|
|
||||||
function showMore(): void {
|
function showMore(): void {
|
||||||
@@ -197,7 +195,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const files = store.fetch("files", filter, {
|
const files = store.fetch("files", filter, {
|
||||||
limit: vnode.state["showCount"] || PAGE_SIZE,
|
limit: vnode.state["showCount"] || PAGE_SIZE,
|
||||||
sort: sort,
|
sort: sort
|
||||||
});
|
});
|
||||||
|
|
||||||
const count = store.count("files", filter);
|
const count = store.count("files", filter);
|
||||||
@@ -214,7 +212,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
attrs["downloadUrl"] = downloadUrl;
|
attrs["downloadUrl"] = downloadUrl;
|
||||||
|
|
||||||
if (window.authorizer.hasAccess("files", 3)) {
|
if (window.authorizer.hasAccess("files", 3)) {
|
||||||
attrs["actionsCallback"] = (selected: Set<string>): Children => {
|
attrs["actionsCallback"] = (selected): Children => {
|
||||||
return [
|
return [
|
||||||
m(
|
m(
|
||||||
"button.primary",
|
"button.primary",
|
||||||
@@ -227,9 +225,9 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
actionHandler: (action, object) => {
|
actionHandler: (action, object) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
putActionHandler(action, object)
|
putActionHandler(action, object)
|
||||||
.then((errors) => {
|
.then(errors => {
|
||||||
const errorList = errors
|
const errorList = errors
|
||||||
? Object.values(errors)
|
? Object.values(errors)
|
||||||
: [];
|
: [];
|
||||||
@@ -241,19 +239,19 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
formData
|
formData
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
overlay.open(cb);
|
overlay.open(cb);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"New"
|
"New"
|
||||||
),
|
),
|
||||||
@@ -262,7 +260,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
{
|
{
|
||||||
title: "Delete selected files",
|
title: "Delete selected files",
|
||||||
disabled: !selected.size,
|
disabled: !selected.size,
|
||||||
onclick: (e) => {
|
onclick: e => {
|
||||||
if (
|
if (
|
||||||
!confirm(`Deleting ${selected.size} files. Are you sure?`)
|
!confirm(`Deleting ${selected.size} files. Are you sure?`)
|
||||||
)
|
)
|
||||||
@@ -271,25 +269,25 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
Promise.all(
|
Promise.all(
|
||||||
Array.from(selected).map((id) =>
|
Array.from(selected).map(id =>
|
||||||
store.deleteResource("files", id)
|
store.deleteResource("files", id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
notifications.push(
|
notifications.push(
|
||||||
"success",
|
"success",
|
||||||
`${res.length} files deleted`
|
`${res.length} files deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.fulfill(0, Date.now());
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"Delete"
|
"Delete"
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -302,8 +300,8 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing files"),
|
m("h1", "Listing files"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(indexTableComponent, attrs)
|
||||||
];
|
];
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+17
-28
@@ -24,30 +24,23 @@ import Autocomplete from "./autocomplete-compnent";
|
|||||||
import * as smartQuery from "./smart-query";
|
import * as smartQuery from "./smart-query";
|
||||||
import { filterToMongoQuery } from "../lib/mongodb-functions";
|
import { filterToMongoQuery } from "../lib/mongodb-functions";
|
||||||
import { Expression } from "../lib/types";
|
import { Expression } from "../lib/types";
|
||||||
import { normalize } from "../lib/common/boolean-expression";
|
|
||||||
|
|
||||||
const getAutocomplete = memoize((resource) => {
|
const getAutocomplete = memoize(resource => {
|
||||||
const labels = smartQuery.getLabels(resource);
|
const labels = smartQuery.getLabels(resource);
|
||||||
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
|
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
|
||||||
txt = txt.toLowerCase();
|
txt = txt.toLowerCase();
|
||||||
cb(
|
cb(
|
||||||
labels
|
labels
|
||||||
.filter((s) => s.toLowerCase().includes(txt))
|
.filter(s => s.toLowerCase().includes(txt))
|
||||||
.map((s) => ({
|
.map(s => ({
|
||||||
value: `${s}: `,
|
value: `${s}: `,
|
||||||
tip: smartQuery.getTip(resource, s),
|
tip: smartQuery.getTip(resource, s)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return autocomplete;
|
return autocomplete;
|
||||||
});
|
});
|
||||||
|
|
||||||
function validateQuery(q: Expression): void {
|
|
||||||
q = normalize(q);
|
|
||||||
if (Array.isArray(q) && q[0] === "CASE") q = ["AND", ...q.slice(1)];
|
|
||||||
filterToMongoQuery(q);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFilter(resource, f): Expression {
|
function parseFilter(resource, f): Expression {
|
||||||
let exp;
|
let exp;
|
||||||
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
||||||
@@ -58,13 +51,11 @@ function parseFilter(resource, f): Expression {
|
|||||||
exp = parse(f);
|
exp = parse(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throws exception if invalid Mongo query
|
// Throw exception if invalid Mongo query
|
||||||
validateQuery(
|
filterToMongoQuery(
|
||||||
map(exp, (e) => {
|
map(exp, e => {
|
||||||
if (Array.isArray(e) && e[0] === "FUNC") {
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
||||||
if (e[1] === "Q") return smartQuery.unpack(resource, e[2], e[3]);
|
return smartQuery.unpack(resource, e[2], e[3]);
|
||||||
else if (e[1] === "NOW") return Date.now();
|
|
||||||
}
|
|
||||||
return e;
|
return e;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -78,7 +69,7 @@ function stringifyFilter(f: Expression): string {
|
|||||||
return stringify(f);
|
return stringify(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
const splitFilter = memoize((filter) => {
|
const splitFilter = memoize(filter => {
|
||||||
if (!filter) return [""];
|
if (!filter) return [""];
|
||||||
const list = [];
|
const list = [];
|
||||||
const f = parse(filter);
|
const f = parse(filter);
|
||||||
@@ -92,7 +83,7 @@ const splitFilter = memoize((filter) => {
|
|||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: vnode => {
|
||||||
if (
|
if (
|
||||||
!vnode.state["filterList"] ||
|
!vnode.state["filterList"] ||
|
||||||
vnode.attrs["filter"] !== vnode.state["filter"]
|
vnode.attrs["filter"] !== vnode.state["filter"]
|
||||||
@@ -104,7 +95,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
function onChange(): void {
|
function onChange(): void {
|
||||||
vnode.state["filterInvalid"] = 0;
|
vnode.state["filterInvalid"] = 0;
|
||||||
vnode.state["filterList"] = vnode.state["filterList"].filter((f) => f);
|
vnode.state["filterList"] = vnode.state["filterList"].filter(f => f);
|
||||||
let filter = vnode.state["filterList"].map((f, idx) => {
|
let filter = vnode.state["filterList"].map((f, idx) => {
|
||||||
try {
|
try {
|
||||||
return parseFilter(vnode.attrs["resource"], f);
|
return parseFilter(vnode.attrs["resource"], f);
|
||||||
@@ -137,21 +128,19 @@ const component: ClosureComponent = (): Component => {
|
|||||||
(vnode.state["filterInvalid"] >> idx) & 1 ? "error" : ""
|
(vnode.state["filterInvalid"] >> idx) & 1 ? "error" : ""
|
||||||
}`,
|
}`,
|
||||||
value: fltr,
|
value: fltr,
|
||||||
onchange: (e) => {
|
onchange: e => {
|
||||||
vnode.state["filterList"] = vnode.state["filterList"].slice();
|
vnode.state["filterList"] = vnode.state["filterList"].slice();
|
||||||
vnode.state["filterList"][idx] = e.target.value.trim();
|
vnode.state["filterList"][idx] = e.target.value.trim();
|
||||||
onChange();
|
onChange();
|
||||||
},
|
},
|
||||||
oncreate: (vn) => {
|
oncreate: vn => {
|
||||||
getAutocomplete(vnode.attrs["resource"]).attach(
|
getAutocomplete(vnode.attrs["resource"]).attach(vn.dom);
|
||||||
vn.dom as HTMLInputElement
|
}
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user