Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc723c0b50 | ||
|
|
a2a6fd82f3 | ||
|
|
729beba0ed | ||
|
|
1b14387aa2 | ||
|
|
49cdd39617 | ||
|
|
4be39ed742 | ||
|
|
8ddb692370 | ||
|
|
fc80a7d487 | ||
|
|
f34f10edc7 | ||
|
|
a416f68b76 | ||
|
|
2b61c8f7b8 | ||
|
|
756912eeb1 | ||
|
|
5c4fc3263b | ||
|
|
307b9f57b0 |
No files matched your search
@@ -1,5 +1,25 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 1.2.14 (2026-03-12)
|
||||||
|
|
||||||
|
- Prevent UI crash when a malformed URL is sent to the server.
|
||||||
|
|
||||||
|
- Fix potential edge-case bugs in expression evaluation and session
|
||||||
|
serialization.
|
||||||
|
|
||||||
|
## 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)
|
## 1.2.12 (2024-03-28)
|
||||||
|
|
||||||
- Fix broken XMPP support in the previous release.
|
- Fix broken XMPP support in the previous release.
|
||||||
|
|||||||
+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
|
||||||
@@ -2,13 +2,13 @@ import { PermissionSet, Expression } from "../types.ts";
|
|||||||
import { evaluate, or } from "./expression/util.ts";
|
import { evaluate, or } from "./expression/util.ts";
|
||||||
|
|
||||||
export default class Authorizer {
|
export default class Authorizer {
|
||||||
private declare permissionSets: PermissionSet[];
|
declare private permissionSets: PermissionSet[];
|
||||||
private declare validatorCache: WeakMap<
|
declare private validatorCache: WeakMap<
|
||||||
any,
|
any,
|
||||||
(mutationType, mutation, any) => boolean
|
(mutationType, mutation, any) => boolean
|
||||||
>;
|
>;
|
||||||
private declare hasAccessCache: Map<string, boolean>;
|
declare private hasAccessCache: Map<string, boolean>;
|
||||||
private declare getFilterCache: Map<string, Expression>;
|
declare private getFilterCache: Map<string, Expression>;
|
||||||
|
|
||||||
public constructor(permissionSets: PermissionSet[]) {
|
public constructor(permissionSets: PermissionSet[]) {
|
||||||
this.permissionSets = permissionSets;
|
this.permissionSets = permissionSets;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ const TWO = BI.BigInt(2);
|
|||||||
const NEGATIVE_ONE = BI.BigInt(-1);
|
const NEGATIVE_ONE = BI.BigInt(-1);
|
||||||
|
|
||||||
class Indeterminates {
|
class Indeterminates {
|
||||||
public declare map: Map<string, number>;
|
declare public map: Map<string, number>;
|
||||||
public declare sortedKeys: string[];
|
declare public sortedKeys: string[];
|
||||||
|
|
||||||
public constructor(str?: string) {
|
public constructor(str?: string) {
|
||||||
this.map = new Map();
|
this.map = new Map();
|
||||||
@@ -98,7 +98,7 @@ function findGcd(a: BI.bigint, b: BI.bigint): BI.bigint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Polynomial {
|
class Polynomial {
|
||||||
public declare terms: Term[];
|
declare public terms: Term[];
|
||||||
|
|
||||||
public constructor(terms: Term[]) {
|
public constructor(terms: Term[]) {
|
||||||
this.terms = terms;
|
this.terms = terms;
|
||||||
|
|||||||
@@ -955,7 +955,7 @@ export function covers(expr1: Expression, expr2: Expression): boolean {
|
|||||||
expr2 = normalize(expr2);
|
expr2 = normalize(expr2);
|
||||||
if (!expr2) return true;
|
if (!expr2) return true;
|
||||||
expr1 = normalize(expr1);
|
expr1 = normalize(expr1);
|
||||||
if (!Array.isArray(expr1)) return !!expr1;
|
if (!Array.isArray(expr1) && expr1) return true;
|
||||||
|
|
||||||
const synt1 = Clause.fromExpression(expr1);
|
const synt1 = Clause.fromExpression(expr1);
|
||||||
const synt2 = Clause.fromExpression(expr2);
|
const synt2 = Clause.fromExpression(expr2);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Path from "./path.ts";
|
import Path from "./path.ts";
|
||||||
|
|
||||||
export default class PathSet {
|
export default class PathSet {
|
||||||
private declare lengthIndex: Set<Path>[];
|
declare private lengthIndex: Set<Path>[];
|
||||||
private declare fragmentIndex: Map<string, Set<Path>>[];
|
declare private fragmentIndex: Map<string, Set<Path>>[];
|
||||||
private declare stringIndex: Map<string, Path>;
|
declare private stringIndex: Map<string, Path>;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.lengthIndex = [];
|
this.lengthIndex = [];
|
||||||
|
|||||||
+5
-5
@@ -30,11 +30,11 @@ function legalChar(c: number): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Path {
|
export default class Path {
|
||||||
public declare readonly segments: Segments;
|
declare public readonly segments: Segments;
|
||||||
public declare readonly wildcard: number;
|
declare public readonly wildcard: number;
|
||||||
public declare readonly alias: number;
|
declare public readonly alias: number;
|
||||||
protected declare _string: string;
|
declare protected _string: string;
|
||||||
protected declare _stringIndex: number[];
|
declare protected _stringIndex: number[];
|
||||||
|
|
||||||
protected static parseAliasValue(
|
protected static parseAliasValue(
|
||||||
pattern: string,
|
pattern: string,
|
||||||
|
|||||||
+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"] ??= [];
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ interface Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class InstanceSet {
|
export default class InstanceSet {
|
||||||
private declare set: Set<Instance>;
|
declare private set: Set<Instance>;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.set = new Set();
|
this.set = new Set();
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { constants } from "node:zlib";
|
import { constants } from "node:zlib";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import Router from "koa-router";
|
import Router from "@koa/router";
|
||||||
import * as jwt from "jsonwebtoken";
|
import * as jwt from "jsonwebtoken";
|
||||||
import koaStatic from "koa-static";
|
import koaSend from "koa-send";
|
||||||
import koaCompress from "koa-compress";
|
import koaCompress from "koa-compress";
|
||||||
import koaBodyParser from "koa-bodyparser";
|
import koaBodyParser from "@koa/bodyparser";
|
||||||
import koaJwt from "koa-jwt";
|
import koaJwt from "koa-jwt";
|
||||||
import * as config from "./config.ts";
|
import * as config from "./config.ts";
|
||||||
import api from "./ui/api.ts";
|
import api from "./ui/api.ts";
|
||||||
@@ -18,12 +18,6 @@ import { version as VERSION } from "../package.json";
|
|||||||
import memoize from "./common/memoize.ts";
|
import memoize from "./common/memoize.ts";
|
||||||
import { APP_JS, APP_CSS, FAVICON_PNG } from "../build/assets.ts";
|
import { APP_JS, APP_CSS, FAVICON_PNG } from "../build/assets.ts";
|
||||||
|
|
||||||
declare module "koa" {
|
|
||||||
interface Request {
|
|
||||||
body: any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const koa = new Koa();
|
const koa = new Koa();
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
@@ -262,6 +256,17 @@ koa.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
koa.use(router.routes());
|
koa.use(router.routes());
|
||||||
koa.use(koaStatic(config.ROOT_DIR + "/public"));
|
koa.use(async (ctx, next) => {
|
||||||
|
await next();
|
||||||
|
if (ctx.method !== "HEAD" && ctx.method !== "GET") return;
|
||||||
|
if (ctx.body != null || ctx.status !== 404) return;
|
||||||
|
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(ctx.path)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await koaSend(ctx, ctx.path, { root: config.ROOT_DIR + "/public" });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status !== 404) throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const listener = koa.callback();
|
export const listener = koa.callback();
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Readable } 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";
|
||||||
import * as apiFunctions from "../api-functions.ts";
|
import * as apiFunctions from "../api-functions.ts";
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ interface Revisions<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class VersionedMap<K, V> {
|
export default class VersionedMap<K, V> {
|
||||||
private declare _sizeDiff: number[];
|
declare private _sizeDiff: number[];
|
||||||
private declare _revision: number;
|
declare private _revision: number;
|
||||||
private declare map: Map<K, (V | symbol)[]>;
|
declare private map: Map<K, (V | symbol)[]>;
|
||||||
public declare dirty: number;
|
declare public dirty: number;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this._sizeDiff = [0];
|
this._sizeDiff = [0];
|
||||||
@@ -112,6 +112,8 @@ export default class VersionedMap<K, V> {
|
|||||||
prev = v;
|
prev = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prev === NONEXISTENT && !res.delete) return null;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +124,8 @@ export default class VersionedMap<K, V> {
|
|||||||
del,
|
del,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!mutations) return;
|
||||||
|
|
||||||
const revisions = [];
|
const revisions = [];
|
||||||
|
|
||||||
let prev: V | symbol = NONEXISTENT;
|
let prev: V | symbol = NONEXISTENT;
|
||||||
@@ -149,7 +153,7 @@ export default class VersionedMap<K, V> {
|
|||||||
|
|
||||||
public *diff(): IterableIterator<[K, V, V]> {
|
public *diff(): IterableIterator<[K, V, V]> {
|
||||||
for (const [key, revisions] of this.map) {
|
for (const [key, revisions] of this.map) {
|
||||||
if (revisions.length === 1) continue;
|
if (revisions.length <= 1) continue;
|
||||||
let first = revisions[0];
|
let first = revisions[0];
|
||||||
let last = revisions[revisions.length - 1];
|
let last = revisions[revisions.length - 1];
|
||||||
if (first === NONEXISTENT && last === NONEXISTENT) continue;
|
if (first === NONEXISTENT && last === NONEXISTENT) continue;
|
||||||
|
|||||||
Generated
+3032
-2922
File diff suppressed because it is too large.
Load diff
+24
-26
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.12",
|
"version": "1.2.14",
|
||||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -26,43 +26,41 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/later": "^4.2.0",
|
"@breejs/later": "^4.2.0",
|
||||||
|
"@koa/bodyparser": "^5.1.2",
|
||||||
|
"@koa/router": "^13.1.1",
|
||||||
"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.3.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"koa": "^2.15.2",
|
"koa": "^2.16.4",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-compress": "^5.2.0",
|
||||||
"koa-compress": "^5.1.0",
|
|
||||||
"koa-jwt": "^4.0.3",
|
"koa-jwt": "^4.0.3",
|
||||||
"koa-router": "^12.0.0",
|
"koa-send": "^5.0.1",
|
||||||
"koa-static": "^5.0.0",
|
|
||||||
"mongodb": "^4.16.0",
|
"mongodb": "^4.16.0",
|
||||||
"parsimmon": "^1.18.1",
|
"parsimmon": "^1.18.1",
|
||||||
"seedrandom": "^3.0.5"
|
"seedrandom": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.17",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-compress": "^4.0.6",
|
"@types/koa-compress": "^4.0.7",
|
||||||
"@types/koa-router": "^7.4.8",
|
"@types/mithril": "^2.2.7",
|
||||||
"@types/koa-static": "^4.0.4",
|
"@types/node": "^20.19.37",
|
||||||
"@types/mithril": "^2.2.6",
|
|
||||||
"@types/node": "^20.11.30",
|
|
||||||
"@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.18.0",
|
||||||
"@typescript-eslint/parser": "^7.3.1",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
"codemirror": "^5.65.16",
|
"codemirror": "^5.65.21",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.27.4",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.2",
|
||||||
"mithril": "^2.2.2",
|
"mithril": "^2.3.8",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.8.1",
|
||||||
"sql.js": "^1.10.2",
|
"sql.js": "^1.14.1",
|
||||||
"svgo": "^3.2.0",
|
"svgo": "^3.3.3",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.9.3",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
+35
-1
@@ -1,7 +1,7 @@
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import initSqlJs from "sql.js/dist/sql-asm.js";
|
import initSqlJs from "sql.js/dist/sql-asm.js";
|
||||||
import { minimize, unionDiff } from "../lib/common/expression/synth.ts";
|
import { covers, minimize, unionDiff } from "../lib/common/expression/synth.ts";
|
||||||
import { parse, stringify } from "../lib/common/expression/parser.ts";
|
import { parse, stringify } from "../lib/common/expression/parser.ts";
|
||||||
|
|
||||||
const STRING_VALUES = [null, "", "a", "ab", "ab10", "ab-10"];
|
const STRING_VALUES = [null, "", "a", "ab", "ab10", "ab-10"];
|
||||||
@@ -115,3 +115,37 @@ void test("unionDiff", async () => {
|
|||||||
assert.strictEqual(setsEqual(res4, diffSet), true);
|
assert.strictEqual(setsEqual(res4, diffSet), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void test("covers", async () => {
|
||||||
|
assert.strictEqual(covers(false, false), true);
|
||||||
|
assert.strictEqual(covers(false, parse("decimal > 5 AND decimal < 3")), true);
|
||||||
|
assert.strictEqual(covers(parse("true"), parse("decimal > 0")), true);
|
||||||
|
assert.strictEqual(covers(parse("true"), parse("false")), true);
|
||||||
|
assert.strictEqual(covers(parse("false"), parse("false")), true);
|
||||||
|
assert.strictEqual(covers(parse("false"), parse("decimal > 0")), false);
|
||||||
|
assert.strictEqual(covers(parse("decimal >= 0"), parse("decimal > 0")), true);
|
||||||
|
assert.strictEqual(
|
||||||
|
covers(parse("decimal > 0"), parse("decimal >= 0")),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
["decimal >= 0", "decimal > 0"],
|
||||||
|
["decimal > 0", "decimal > 5"],
|
||||||
|
["string IS NOT NULL", "string = 'a'"],
|
||||||
|
["true", "decimal > 0"],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [c1, c2] of cases) {
|
||||||
|
const res1 = await query(c1);
|
||||||
|
const res2 = await query(c2);
|
||||||
|
const coversResult = covers(parse(c1), parse(c2));
|
||||||
|
const actuallyCovers = Array.from(res2).every((r) => res1.has(r));
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
coversResult,
|
||||||
|
actuallyCovers,
|
||||||
|
`covers(${c1}, ${c2}) should match actual coverage`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
+2
-1
@@ -11,7 +11,8 @@
|
|||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"allowImportingTsExtensions": true
|
"allowImportingTsExtensions": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./bin/*.ts",
|
"./bin/*.ts",
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ type AutocompleteCallback = (
|
|||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export default class Autocomplete {
|
export default class Autocomplete {
|
||||||
private declare callback: AutocompleteCallback;
|
declare private callback: AutocompleteCallback;
|
||||||
private declare element: HTMLInputElement;
|
declare private element: HTMLInputElement;
|
||||||
private declare hideTimeout: NodeJS.Timeout;
|
declare private hideTimeout: NodeJS.Timeout;
|
||||||
private declare visible: boolean;
|
declare private visible: boolean;
|
||||||
private declare default: string;
|
declare private default: string;
|
||||||
private declare selection: number;
|
declare private selection: number;
|
||||||
private declare container: HTMLElement;
|
declare private container: HTMLElement;
|
||||||
|
|
||||||
public constructor(className: string, callback: AutocompleteCallback) {
|
public constructor(className: string, callback: AutocompleteCallback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function structureConfig(config: Config[]): any {
|
|||||||
if (idx >= 0 && idx < MAX_ARRAY_SIZE && String(idx) === key) {
|
if (idx >= 0 && idx < MAX_ARRAY_SIZE && String(idx) === key) {
|
||||||
const pos = Math.floor(idx / MAX_BITS);
|
const pos = Math.floor(idx / MAX_BITS);
|
||||||
if (!indexes[pos]) indexes[pos] = 0;
|
if (!indexes[pos]) indexes[pos] = 0;
|
||||||
indexes[pos] |= 1 << idx % MAX_BITS;
|
indexes[pos] |= 1 << (idx % MAX_BITS);
|
||||||
} else {
|
} else {
|
||||||
indexes = [];
|
indexes = [];
|
||||||
break;
|
break;
|
||||||
|
|||||||
+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