Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc80a7d487 | ||
|
|
f34f10edc7 | ||
|
|
a416f68b76 | ||
|
|
2b61c8f7b8 | ||
|
|
756912eeb1 | ||
|
|
5c4fc3263b | ||
|
|
307b9f57b0 | ||
|
|
0f6604ffbd | ||
|
|
f57a26c95b | ||
|
|
60e6d8b175 | ||
|
|
ab80a62c5c | ||
|
|
3134b63b68 | ||
|
|
2b1bca7dde |
No files matched your search
@@ -1,5 +1,25 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 1.2.13 (2024-06-06)
|
||||||
|
|
||||||
|
- Increase connection timeout for UI and NBI from 30 to 120 seconds to avoid
|
||||||
|
timeouts when running unindexed queries in large deployments.
|
||||||
|
|
||||||
|
- Fix race condition causing 503 error when deleting multiple faults at once.
|
||||||
|
|
||||||
|
- Fix some UI config options not being evaluated as dynamic expressions.
|
||||||
|
|
||||||
|
- Fix an issue where certain edge-case query expressions were not being
|
||||||
|
correctly converted to MongoDB queries, resulting in inaccurate search
|
||||||
|
results.
|
||||||
|
|
||||||
|
## 1.2.12 (2024-03-28)
|
||||||
|
|
||||||
|
- Fix broken XMPP support in the previous release.
|
||||||
|
|
||||||
|
- Fix regression causing CSV downloads to be buffered in memory before being
|
||||||
|
streamed to the client.
|
||||||
|
|
||||||
## 1.2.11 (2024-03-21)
|
## 1.2.11 (2024-03-21)
|
||||||
|
|
||||||
- Resolved an issue from the previous release that caused incompatibility with
|
- Resolved an issue from the previous release that caused incompatibility with
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ if (!cluster.worker) {
|
|||||||
port: SERVICE_PORT,
|
port: SERVICE_PORT,
|
||||||
host: SERVICE_ADDRESS,
|
host: SERVICE_ADDRESS,
|
||||||
ssl: key && cert ? { key, cert } : null,
|
ssl: key && cert ? { key, cert } : null,
|
||||||
timeout: 30000,
|
timeout: 120000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Need this for Node < 15
|
// Need this for Node < 15
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ if (!cluster.worker) {
|
|||||||
port: SERVICE_PORT,
|
port: SERVICE_PORT,
|
||||||
host: SERVICE_ADDRESS,
|
host: SERVICE_ADDRESS,
|
||||||
ssl: key && cert ? { key, cert } : null,
|
ssl: key && cert ? { key, cert } : null,
|
||||||
timeout: 30000,
|
timeout: 120000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Need this for Node < 15
|
// Need this for Node < 15
|
||||||
|
|||||||
@@ -177,3 +177,4 @@ CIDR
|
|||||||
XMPP
|
XMPP
|
||||||
PEM
|
PEM
|
||||||
JID
|
JID
|
||||||
|
unindexed
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Read the Docs configuration file for Sphinx projects
|
||||||
|
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
tools:
|
||||||
|
python: "3.12"
|
||||||
|
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
|
||||||
|
formats: all
|
||||||
|
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
+2
-2
@@ -19,7 +19,7 @@ import json
|
|||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'GenieACS Documentation'
|
project = 'GenieACS Documentation'
|
||||||
copyright = '2019, GenieACS Inc.'
|
copyright = '2024, GenieACS Inc.'
|
||||||
author = 'GenieACS Inc.'
|
author = 'GenieACS Inc.'
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
@@ -58,7 +58,7 @@ html_theme_options = {
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
# html_static_path = ['_static']
|
||||||
|
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
sphinx_rtd_theme==2.0.0
|
||||||
+19
-7
@@ -578,6 +578,8 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
|
|||||||
toQuery(minterms: Minterm[]): Filter<unknown> {
|
toQuery(minterms: Minterm[]): Filter<unknown> {
|
||||||
const or = [];
|
const or = [];
|
||||||
|
|
||||||
|
const ejsonOps = ["$oid", "$date", "$regularExpression"];
|
||||||
|
|
||||||
for (const minterm of minterms) {
|
for (const minterm of minterms) {
|
||||||
const query = {};
|
const query = {};
|
||||||
loop: for (const clause of minterm) {
|
loop: for (const clause of minterm) {
|
||||||
@@ -588,8 +590,6 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
|
|||||||
if (Object.keys(q).length !== 1)
|
if (Object.keys(q).length !== 1)
|
||||||
throw new Error("Invalid query expression");
|
throw new Error("Invalid query expression");
|
||||||
const [param, value] = Object.entries(q)[0];
|
const [param, value] = Object.entries(q)[0];
|
||||||
if (Object.getPrototypeOf(value).constructor !== Object)
|
|
||||||
throw new Error("Invalid query expression");
|
|
||||||
const dests = [query, ...(query["$and"] ?? [])];
|
const dests = [query, ...(query["$and"] ?? [])];
|
||||||
for (const dest of dests) {
|
for (const dest of dests) {
|
||||||
if (!(param in dest)) {
|
if (!(param in dest)) {
|
||||||
@@ -599,16 +599,28 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
|
|||||||
|
|
||||||
let src = value;
|
let src = value;
|
||||||
let dst = dest[param];
|
let dst = dest[param];
|
||||||
|
if (Object.getPrototypeOf(src).constructor !== Object) continue;
|
||||||
|
if (Object.getPrototypeOf(dst).constructor !== Object) continue;
|
||||||
|
let srcKeys = Object.keys(src);
|
||||||
|
let dstKeys = Object.keys(dst);
|
||||||
|
|
||||||
if (src["$not"] && dst["$not"]) {
|
if ([...srcKeys, ...dstKeys].every((k) => k === "$not")) {
|
||||||
src = src["$not"];
|
src = src["$not"];
|
||||||
dst = dst["$not"];
|
dst = dst["$not"];
|
||||||
|
if (Object.getPrototypeOf(src).constructor !== Object) continue;
|
||||||
|
if (Object.getPrototypeOf(dst).constructor !== Object) continue;
|
||||||
|
srcKeys = Object.keys(src);
|
||||||
|
dstKeys = Object.keys(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Object.keys(src).some((k) => k in dst)) {
|
if (srcKeys.some((k) => dstKeys.includes(k))) continue;
|
||||||
Object.assign(dst, src);
|
|
||||||
continue loop;
|
// Don't mix regular operators with EJSON special operators
|
||||||
}
|
if (srcKeys.some((k) => ejsonOps.includes(k))) continue;
|
||||||
|
if (dstKeys.some((k) => ejsonOps.includes(k))) continue;
|
||||||
|
|
||||||
|
Object.assign(dst, src);
|
||||||
|
continue loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
query["$and"] ??= [];
|
query["$and"] ??= [];
|
||||||
|
|||||||
+66
-64
@@ -1,4 +1,4 @@
|
|||||||
import { PassThrough } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import * as db from "./db.ts";
|
import * as db from "./db.ts";
|
||||||
@@ -88,10 +88,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
|
|||||||
.replace(/[:.]/g, "")}.csv`,
|
.replace(/[:.]/g, "")}.csv`,
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = new PassThrough();
|
const lines: string[] = [
|
||||||
ctx.body.write(
|
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp",
|
||||||
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp\n",
|
];
|
||||||
);
|
|
||||||
|
|
||||||
for (const k of Object.keys(device).sort()) {
|
for (const k of Object.keys(device).sort()) {
|
||||||
const p = device[k];
|
const p = device[k];
|
||||||
@@ -118,9 +117,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
|
|||||||
p.accessList ? p.accessList.join(", ") : "",
|
p.accessList ? p.accessList.join(", ") : "",
|
||||||
new Date(p.accessListTimestamp).toJSON(),
|
new Date(p.accessListTimestamp).toJSON(),
|
||||||
];
|
];
|
||||||
ctx.body.write(row.map((r) => (r != null ? r : "")).join(",") + "\n");
|
lines.push(row.map((r) => (r != null ? r : "")).join(","));
|
||||||
}
|
}
|
||||||
ctx.body.end();
|
ctx.body = lines.join("\n");
|
||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,16 +200,17 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = new PassThrough();
|
|
||||||
ctx.type = "application/json";
|
|
||||||
|
|
||||||
let c = 0;
|
|
||||||
ctx.body.write("[\n");
|
|
||||||
for await (const obj of db.query(resource, filter, options))
|
|
||||||
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
|
|
||||||
|
|
||||||
ctx.body.end("]");
|
|
||||||
logger.accessInfo(log);
|
logger.accessInfo(log);
|
||||||
|
ctx.type = "application/json";
|
||||||
|
ctx.body = Readable.from(
|
||||||
|
(async function* () {
|
||||||
|
let c = 0;
|
||||||
|
yield "[\n";
|
||||||
|
for await (const obj of db.query(resource, filter, options))
|
||||||
|
yield (c++ ? "," : "") + JSON.stringify(obj) + "\n";
|
||||||
|
yield "]";
|
||||||
|
})(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// CSV download
|
// CSV download
|
||||||
@@ -260,60 +260,62 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = new PassThrough();
|
logger.accessInfo(log);
|
||||||
ctx.type = "text/csv";
|
ctx.type = "text/csv";
|
||||||
ctx.attachment(
|
ctx.attachment(
|
||||||
`${resource}-${new Date(now).toISOString().replace(/[:.]/g, "")}.csv`,
|
`${resource}-${new Date(now).toISOString().replace(/[:.]/g, "")}.csv`,
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body.write(
|
ctx.body = Readable.from(
|
||||||
Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n",
|
(async function* () {
|
||||||
|
yield Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) +
|
||||||
|
"\n";
|
||||||
|
for await (const obj of db.query(resource, filter, options)) {
|
||||||
|
const arr = Object.values(columns).map((exp) => {
|
||||||
|
const v = evaluate(exp, obj, null, (e) => {
|
||||||
|
if (Array.isArray(e)) {
|
||||||
|
if (e[0] === "PARAM") {
|
||||||
|
if (resource === "devices") {
|
||||||
|
if (e[1] === "Tags") {
|
||||||
|
const tags = [];
|
||||||
|
for (const p in obj)
|
||||||
|
if (p.startsWith("Tags."))
|
||||||
|
tags.push(decodeTag(p.slice(5)));
|
||||||
|
|
||||||
|
return tags.join(", ");
|
||||||
|
}
|
||||||
|
if (e === exp) {
|
||||||
|
const p = obj[e[1]];
|
||||||
|
if (
|
||||||
|
p &&
|
||||||
|
p.value &&
|
||||||
|
p.value[1] === "xsd:dateTime" &&
|
||||||
|
typeof p.value[0] === "number"
|
||||||
|
)
|
||||||
|
return new Date(p.value[0]).toJSON();
|
||||||
|
}
|
||||||
|
} else if (resource === "faults") {
|
||||||
|
if (e[1] === "detail") return yamlStringify(obj["detail"]);
|
||||||
|
}
|
||||||
|
} else if (e[0] === "FUNC") {
|
||||||
|
if (e[1] === "DATE_STRING") {
|
||||||
|
if (e[2] && !Array.isArray(e[2]))
|
||||||
|
return new Date(e[2]).toJSON();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(v) || v == null) return "";
|
||||||
|
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
yield arr.join(",") + "\n";
|
||||||
|
}
|
||||||
|
})(),
|
||||||
);
|
);
|
||||||
for await (const obj of db.query(resource, filter, options)) {
|
|
||||||
const arr = Object.values(columns).map((exp) => {
|
|
||||||
const v = evaluate(exp, obj, null, (e) => {
|
|
||||||
if (Array.isArray(e)) {
|
|
||||||
if (e[0] === "PARAM") {
|
|
||||||
if (resource === "devices") {
|
|
||||||
if (e[1] === "Tags") {
|
|
||||||
const tags = [];
|
|
||||||
for (const p in obj)
|
|
||||||
if (p.startsWith("Tags.")) tags.push(decodeTag(p.slice(5)));
|
|
||||||
|
|
||||||
return tags.join(", ");
|
|
||||||
}
|
|
||||||
if (e === exp) {
|
|
||||||
const p = obj[e[1]];
|
|
||||||
if (
|
|
||||||
p &&
|
|
||||||
p.value &&
|
|
||||||
p.value[1] === "xsd:dateTime" &&
|
|
||||||
typeof p.value[0] === "number"
|
|
||||||
)
|
|
||||||
return new Date(p.value[0]).toJSON();
|
|
||||||
}
|
|
||||||
} else if (resource === "faults") {
|
|
||||||
if (e[1] === "detail") return yamlStringify(obj["detail"]);
|
|
||||||
}
|
|
||||||
} else if (e[0] === "FUNC") {
|
|
||||||
if (e[1] === "DATE_STRING") {
|
|
||||||
if (e[2] && !Array.isArray(e[2]))
|
|
||||||
return new Date(e[2]).toJSON();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Array.isArray(v) || v == null) return "";
|
|
||||||
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
ctx.body.write(arr.join(",") + "\n");
|
|
||||||
}
|
|
||||||
ctx.body.end();
|
|
||||||
logger.accessInfo(log);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.head(`/${resource}/:id`, async (ctx) => {
|
router.head(`/${resource}/:id`, async (ctx) => {
|
||||||
|
|||||||
+36
-42
@@ -321,50 +321,44 @@ export default class XmppClient extends EventEmitter {
|
|||||||
this._iqStanzaCallbacks = new Map();
|
this._iqStanzaCallbacks = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
static connect(opts: XmppClientOptions): Promise<XmppClient> {
|
static async connect(opts: XmppClientOptions): Promise<XmppClient> {
|
||||||
return new Promise((resolve, reject) => {
|
function connectSocket(host: string, port: number): Promise<net.Socket> {
|
||||||
let socket = new net.Socket();
|
return new Promise((resolve, reject) => {
|
||||||
socket.on("error", reject);
|
const socket = new net.Socket();
|
||||||
|
socket.on("error", reject);
|
||||||
socket.connect(
|
socket.connect(port, host, () => {
|
||||||
opts.port || 5222,
|
|
||||||
opts.host,
|
|
||||||
void async function () {
|
|
||||||
socket.removeListener("error", reject);
|
socket.removeListener("error", reject);
|
||||||
try {
|
resolve(socket);
|
||||||
let status = 1;
|
});
|
||||||
while (status) {
|
});
|
||||||
if (status === STATUS_STARTTLS)
|
}
|
||||||
socket = await upgradeTls(socket, opts.host);
|
|
||||||
status = await xmppStream(
|
|
||||||
socket,
|
|
||||||
init(
|
|
||||||
socket,
|
|
||||||
opts.host,
|
|
||||||
opts.username,
|
|
||||||
opts.password,
|
|
||||||
opts.resource,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new XmppClient();
|
let socket = await connectSocket(opts.host, opts.port || 5222);
|
||||||
client._socket = socket;
|
try {
|
||||||
client._host = opts.host;
|
let status = 1;
|
||||||
client._username = opts.username;
|
while (status) {
|
||||||
client._resource = opts.resource;
|
if (status === STATUS_STARTTLS)
|
||||||
socket.on("data", client._onData.bind(client));
|
socket = await upgradeTls(socket, opts.host);
|
||||||
socket.on("error", client._onError.bind(client));
|
status = await xmppStream(
|
||||||
if (opts.timeout)
|
socket,
|
||||||
socket.setTimeout(opts.timeout, client.close.bind(client));
|
init(socket, opts.host, opts.username, opts.password, opts.resource),
|
||||||
resolve(client);
|
);
|
||||||
} catch (err) {
|
}
|
||||||
socket.destroy();
|
} catch (err) {
|
||||||
reject(err);
|
socket.destroy();
|
||||||
}
|
throw err;
|
||||||
},
|
}
|
||||||
);
|
|
||||||
});
|
const client = new XmppClient();
|
||||||
|
client._socket = socket;
|
||||||
|
client._host = opts.host;
|
||||||
|
client._username = opts.username;
|
||||||
|
client._resource = opts.resource;
|
||||||
|
socket.on("data", client._onData.bind(client));
|
||||||
|
socket.on("error", client._onError.bind(client));
|
||||||
|
if (opts.timeout)
|
||||||
|
socket.setTimeout(opts.timeout, client.close.bind(client));
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
|
|||||||
Generated
+372
-406
File diff suppressed because it is too large.
Load diff
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.11",
|
"version": "1.2.13",
|
||||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
"bson": "^4.7.2",
|
"bson": "^4.7.2",
|
||||||
"espresso-iisojs": "^1.0.8",
|
"espresso-iisojs": "^1.0.8",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.2.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"koa": "^2.15.2",
|
"koa": "^2.15.3",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
"koa-compress": "^5.1.0",
|
"koa-compress": "^5.1.0",
|
||||||
"koa-jwt": "^4.0.3",
|
"koa-jwt": "^4.0.3",
|
||||||
@@ -49,20 +49,20 @@
|
|||||||
"@types/koa-router": "^7.4.8",
|
"@types/koa-router": "^7.4.8",
|
||||||
"@types/koa-static": "^4.0.4",
|
"@types/koa-static": "^4.0.4",
|
||||||
"@types/mithril": "^2.2.6",
|
"@types/mithril": "^2.2.6",
|
||||||
"@types/node": "^20.11.30",
|
"@types/node": "^20.14.2",
|
||||||
"@types/parsimmon": "^1.10.9",
|
"@types/parsimmon": "^1.10.9",
|
||||||
"@types/seedrandom": "^3.0.8",
|
"@types/seedrandom": "^3.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"@typescript-eslint/eslint-plugin": "^7.12.0",
|
||||||
"@typescript-eslint/parser": "^7.3.1",
|
"@typescript-eslint/parser": "^7.12.0",
|
||||||
"codemirror": "^5.65.16",
|
"codemirror": "^5.65.16",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.4",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"mithril": "^2.2.2",
|
"mithril": "^2.2.2",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.3.1",
|
||||||
"sql.js": "^1.10.2",
|
"sql.js": "^1.10.3",
|
||||||
"svgo": "^3.2.0",
|
"svgo": "^3.3.2",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.4.5",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -27,10 +27,14 @@ const component: ClosureComponent = (): Component => {
|
|||||||
cx: "0.5em",
|
cx: "0.5em",
|
||||||
cy: "0.5em",
|
cy: "0.5em",
|
||||||
r: "0.4em",
|
r: "0.4em",
|
||||||
fill: slice["color"],
|
fill: evaluateExpression(slice["color"], null),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return m("span.overview-dot", dot, `${slice["label"]}`);
|
return m(
|
||||||
|
"span.overview-dot",
|
||||||
|
dot,
|
||||||
|
evaluateExpression(slice["label"], null),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+5
-1
@@ -39,7 +39,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
for (const c of Object.values(conf)) {
|
for (const c of Object.values(conf)) {
|
||||||
cmps.push(
|
cmps.push(
|
||||||
m.context({ device: dev.value[0], deviceQuery: dev }, c["type"], c),
|
m.context(
|
||||||
|
{ device: dev.value[0], deviceQuery: dev },
|
||||||
|
store.evaluateExpression(c["type"], null) as string,
|
||||||
|
c,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -26,7 +26,10 @@ 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[store.evaluateExpression(p.label, null) as string] = 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),
|
||||||
@@ -282,7 +285,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
attrs["attributes"] = attributes;
|
attrs["attributes"] = attributes.map((a) => ({
|
||||||
|
...a,
|
||||||
|
label: store.evaluateExpression(a.label, null),
|
||||||
|
type: store.evaluateExpression(a.type, null),
|
||||||
|
}));
|
||||||
attrs["data"] = devs.value;
|
attrs["data"] = devs.value;
|
||||||
attrs["total"] = count.value;
|
attrs["total"] = count.value;
|
||||||
attrs["showMoreCallback"] = showMore;
|
attrs["showMoreCallback"] = showMore;
|
||||||
|
|||||||
+16
-10
@@ -46,6 +46,18 @@ const unpackSmartQuery = memoize((query) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function deleteFaults(faults: Iterable<string>): Promise<void> {
|
||||||
|
const proms: Map<string, Promise<void>> = new Map();
|
||||||
|
for (const f of faults) {
|
||||||
|
const deviceId = f.split(":", 1)[0];
|
||||||
|
let p = proms.get(deviceId);
|
||||||
|
if (p == null) p = store.deleteResource("faults", f);
|
||||||
|
else p = p.then(() => store.deleteResource("faults", f));
|
||||||
|
proms.set(deviceId, p);
|
||||||
|
}
|
||||||
|
await Promise.all(proms.values());
|
||||||
|
}
|
||||||
|
|
||||||
export function init(
|
export function init(
|
||||||
args: Record<string, unknown>,
|
args: Record<string, unknown>,
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
@@ -154,16 +166,10 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
if (!confirm(`Deleting ${selected.size} faults. Are you sure?`))
|
if (!confirm(`Deleting ${selected.size} faults. Are you sure?`))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Promise.all(
|
const c = selected.size;
|
||||||
Array.from(selected).map((id) =>
|
deleteFaults(selected)
|
||||||
store.deleteResource("faults", id),
|
.then(() => {
|
||||||
),
|
notifications.push("success", `${c} faults deleted`);
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
notifications.push(
|
|
||||||
"success",
|
|
||||||
`${res.length} faults deleted`,
|
|
||||||
);
|
|
||||||
store.setTimestamp(Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ClosureComponent, Component, Children } from "mithril";
|
|||||||
import { m } from "./components.ts";
|
import { m } from "./components.ts";
|
||||||
import { stringify } from "../lib/common/expression/parser.ts";
|
import { stringify } from "../lib/common/expression/parser.ts";
|
||||||
import memoize from "../lib/common/memoize.ts";
|
import memoize from "../lib/common/memoize.ts";
|
||||||
|
import * as store from "./store.ts";
|
||||||
|
|
||||||
const memoizedStringify = memoize(stringify);
|
const memoizedStringify = memoize(stringify);
|
||||||
|
|
||||||
@@ -24,9 +25,9 @@ function drawChart(chartData): Children {
|
|||||||
legend.push(
|
legend.push(
|
||||||
m(".legend-line", [
|
m(".legend-line", [
|
||||||
m("span.color", {
|
m("span.color", {
|
||||||
style: `background-color: ${slice["color"]} !important;`,
|
style: `background-color: ${store.evaluateExpression(slice["color"], null)} !important;`,
|
||||||
}),
|
}),
|
||||||
`${slice["label"]}: `,
|
`${store.evaluateExpression(slice["label"], null)}: `,
|
||||||
m(
|
m(
|
||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
@@ -57,7 +58,7 @@ function drawChart(chartData): Children {
|
|||||||
paths.push(
|
paths.push(
|
||||||
m("path", {
|
m("path", {
|
||||||
d: sketch,
|
d: sketch,
|
||||||
fill: slice["color"],
|
fill: store.evaluateExpression(slice["color"], null),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user