Update linting

This commit is contained in:
Zaid Humaimidi committed 2023-12-16 19:08:13 -08:00
1 parent 4f2dfb4f48
commit 0c9c027a77
112 files changed
+3781 -4125

No files matched your search

+1 -3
View File
@@ -1,6 +1,6 @@
{ {
"env": { "env": {
"es6": true, "es2022": true,
"node": true "node": true
}, },
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
@@ -17,7 +17,6 @@
"no-shadow": ["error", { "allow": ["err"] }], "no-shadow": ["error", { "allow": ["err"] }],
"handle-callback-err": "error", "handle-callback-err": "error",
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"curly": ["error", "multi-or-nest", "consistent"],
"no-buffer-constructor": "error", "no-buffer-constructor": "error",
"prefer-const": ["error", { "destructuring": "all" }], "prefer-const": ["error", { "destructuring": "all" }],
"eqeqeq": ["error", "always", { "null": "ignore" }], "eqeqeq": ["error", "always", { "null": "ignore" }],
@@ -32,7 +31,6 @@
], ],
"@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-misused-promises": "error",
"require-atomic-updates": 0,
"no-prototype-builtins": 0 "no-prototype-builtins": 0
} }
} }
+3 -3
View File
@@ -43,7 +43,7 @@ function exitWorkerGracefully(): void {
} }
function exitWorkerUngracefully(): void { function exitWorkerUngracefully(): void {
extensions.killAll().finally(() => { void extensions.killAll().finally(() => {
process.exit(1); process.exit(1);
}); });
} }
@@ -117,7 +117,7 @@ if (!cluster.worker) {
}); });
process.on("SIGINT", () => { process.on("SIGINT", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server server
.stop(false) .stop(false)
.then(exitWorkerGracefully) .then(exitWorkerGracefully)
@@ -126,7 +126,7 @@ if (!cluster.worker) {
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server server
.stop(false) .stop(false)
.then(exitWorkerGracefully) .then(exitWorkerGracefully)
+2 -2
View File
@@ -41,12 +41,12 @@ function errorToFault(err: Error): Fault {
fault.detail["stack"] = err.stack; fault.detail["stack"] = err.stack;
// Trim the stack trace // Trim the stack trace
const stackTrimIndex = fault.detail["stack"].match( const stackTrimIndex = fault.detail["stack"].match(
/\s+at\s[^\s]+\s\(.*genieacs-ext:.+\)/ /\s+at\s[^\s]+\s\(.*genieacs-ext:.+\)/,
); );
if (stackTrimIndex) { if (stackTrimIndex) {
fault.detail["stack"] = fault.detail["stack"].slice( fault.detail["stack"] = fault.detail["stack"].slice(
0, 0,
stackTrimIndex.index stackTrimIndex.index,
); );
} }
} }
+2 -2
View File
@@ -106,13 +106,13 @@ if (!cluster.worker) {
}); });
process.on("SIGINT", () => { process.on("SIGINT", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
+3 -3
View File
@@ -43,7 +43,7 @@ function exitWorkerGracefully(): void {
} }
function exitWorkerUngracefully(): void { function exitWorkerUngracefully(): void {
extensions.killAll().finally(() => { void extensions.killAll().finally(() => {
process.exit(1); process.exit(1);
}); });
} }
@@ -113,13 +113,13 @@ if (!cluster.worker) {
}); });
process.on("SIGINT", () => { process.on("SIGINT", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
+3 -3
View File
@@ -43,7 +43,7 @@ function exitWorkerGracefully(): void {
} }
function exitWorkerUngracefully(): void { function exitWorkerUngracefully(): void {
extensions.killAll().finally(() => { void extensions.killAll().finally(() => {
process.exit(1); process.exit(1);
}); });
} }
@@ -113,13 +113,13 @@ if (!cluster.worker) {
}); });
process.on("SIGINT", () => { process.on("SIGINT", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
initPromise.finally(() => { void initPromise.finally(() => {
server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully); server.stop().then(exitWorkerGracefully).catch(exitWorkerUngracefully);
}); });
}); });
+12 -12
View File
@@ -106,7 +106,7 @@ const assetsPlugin = {
setup(build) { setup(build) {
build.onLoad({ filter: /\/build\/assets.ts$/ }, () => { build.onLoad({ filter: /\/build\/assets.ts$/ }, () => {
const lines = Object.entries(ASSETS).map( const lines = Object.entries(ASSETS).map(
([k, v]) => `export const ${k} = ${JSON.stringify(v)};` ([k, v]) => `export const ${k} = ${JSON.stringify(v)};`,
); );
return { contents: lines.join("\n") }; return { contents: lines.join("\n") };
}); });
@@ -194,11 +194,11 @@ async function init(): Promise<void> {
fsAsync.mkdir(path.join(OUTPUT_DIR, "public")), fsAsync.mkdir(path.join(OUTPUT_DIR, "public")),
fsAsync.writeFile( fsAsync.writeFile(
path.join(OUTPUT_DIR, "package.json"), path.join(OUTPUT_DIR, "package.json"),
JSON.stringify(packageJson, null, 2) JSON.stringify(packageJson, null, 2),
), ),
fsAsync.writeFile( fsAsync.writeFile(
path.join(OUTPUT_DIR, "npm-shrinkwrap.json"), path.join(OUTPUT_DIR, "npm-shrinkwrap.json"),
JSON.stringify(npmShrinkwrap, null, 2) JSON.stringify(npmShrinkwrap, null, 2),
), ),
]); ]);
} }
@@ -228,9 +228,9 @@ async function copyStatic(): Promise<void> {
files.map((f) => files.map((f) =>
fsAsync.copyFile( fsAsync.copyFile(
path.join(INPUT_DIR, f), path.join(INPUT_DIR, f),
path.join(OUTPUT_DIR, filenames[f] || f) path.join(OUTPUT_DIR, filenames[f] || f),
) ),
) ),
); );
} }
@@ -252,7 +252,7 @@ async function generateCss(): Promise<void> {
if (v.entryPoint === "ui/css/app.css") { if (v.entryPoint === "ui/css/app.css") {
ASSETS.APP_CSS = path.relative( ASSETS.APP_CSS = path.relative(
path.join(OUTPUT_DIR, "public"), path.join(OUTPUT_DIR, "public"),
path.join(INPUT_DIR, k) path.join(INPUT_DIR, k),
); );
break; break;
} }
@@ -315,7 +315,7 @@ async function generateFrontendJs(): Promise<void> {
if (v.entryPoint === "ui/app.ts") { if (v.entryPoint === "ui/app.ts") {
ASSETS.APP_JS = path.relative( ASSETS.APP_JS = path.relative(
path.join(OUTPUT_DIR, "public"), path.join(OUTPUT_DIR, "public"),
path.join(INPUT_DIR, k) path.join(INPUT_DIR, k),
); );
break; break;
} }
@@ -344,12 +344,12 @@ async function generateIconsSprite(): Promise<void> {
symbols.push(generateSymbol(id, data)); symbols.push(generateSymbol(id, data));
} }
const data = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join( const data = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join(
"" "",
)}</svg>`; )}</svg>`;
ASSETS.ICONS_SVG = `icons-${assetHash(data)}.svg`; ASSETS.ICONS_SVG = `icons-${assetHash(data)}.svg`;
await fsAsync.writeFile( await fsAsync.writeFile(
path.join(OUTPUT_DIR, "public", ASSETS.ICONS_SVG), path.join(OUTPUT_DIR, "public", ASSETS.ICONS_SVG),
data data,
); );
} }
@@ -357,10 +357,10 @@ init()
.then(() => .then(() =>
Promise.all([ Promise.all([
Promise.all([generateIconsSprite(), copyStatic()]).then( Promise.all([generateIconsSprite(), copyStatic()]).then(
generateFrontendJs generateFrontendJs,
), ),
generateCss(), generateCss(),
]).then(generateBackendJs) ]).then(generateBackendJs),
) )
.catch((err) => { .catch((err) => {
process.stderr.write(err.stack + "\n"); process.stderr.write(err.stack + "\n");
+55
View File
@@ -0,0 +1,55 @@
import { exec } from "child_process";
import { promisify } from "util";
const execPromise = promisify(exec);
async function runEslint(): Promise<string> {
const CMD =
"eslint 'bin/*.ts' 'lib/**/*.ts' 'ui/**/*.ts' 'test/**/*.ts' 'build/**/*.ts'";
const env = {
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
...process.env,
};
try {
const { stdout, stderr } = await execPromise(CMD, { env });
if (stderr) throw new Error(stderr);
return stdout;
} catch (err) {
if (err.killed || err.signal || err.stderr || err.code !== 1) throw err;
return err.stdout;
}
}
async function runTsc(): Promise<string> {
const CMD = "tsc --noEmit";
const env = {
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
...process.env,
};
const { stdout, stderr } = await execPromise(CMD, { env });
if (stderr) throw new Error(stderr);
return stdout;
}
async function runPrettier(): Promise<string> {
const CMD = "prettier --prose-wrap always --write .";
const env = {
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
...process.env,
};
const { stdout, stderr } = await execPromise(CMD, { env });
if (stderr) throw new Error(stderr);
return stdout;
}
async function runAll(): Promise<void> {
const prom1 = runPrettier();
const prom2 = runEslint();
const prom3 = runTsc();
console.log(await prom1);
console.log(await prom2);
console.log(await prom3);
}
runAll().catch(console.error);
+1 -1
View File
@@ -16,7 +16,7 @@ function latlong(args, callback) {
.get("http://api.open-notify.org/iss-now.json", (res) => { .get("http://api.open-notify.org/iss-now.json", (res) => {
if (res.statusCode !== 200) if (res.statusCode !== 200)
return callback( return callback(
new Error(`Request failed (status code: ${res.statusCode})`) new Error(`Request failed (status code: ${res.statusCode})`),
); );
let rawData = ""; let rawData = "";
+16 -16
View File
@@ -57,7 +57,7 @@ import { ResourceLockedError } from "./common/errors";
export async function connectionRequest( export async function connectionRequest(
deviceId: string, deviceId: string,
device?: Record<string, { value?: [boolean | number | string, string] }> device?: Record<string, { value?: [boolean | number | string, string] }>,
): Promise<string> { ): Promise<string> {
if (!device) { if (!device) {
const res = await collections.devices.findOne({ _id: deviceId }); const res = await collections.devices.findOne({ _id: deviceId });
@@ -132,25 +132,25 @@ export async function connectionRequest(
"cwmp.udpConnectionRequestPort", "cwmp.udpConnectionRequestPort",
{}, {},
now, now,
evalCallback evalCallback,
); );
const CONNECTION_REQUEST_TIMEOUT = +getConfig( const CONNECTION_REQUEST_TIMEOUT = +getConfig(
snapshot, snapshot,
"cwmp.connectionRequestTimeout", "cwmp.connectionRequestTimeout",
{}, {},
now, now,
evalCallback evalCallback,
); );
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = !!getConfig( const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = !!getConfig(
snapshot, snapshot,
"cwmp.connectionRequestAllowBasicAuth", "cwmp.connectionRequestAllowBasicAuth",
{}, {},
now, now,
evalCallback evalCallback,
); );
let authExp: Expression = getConfigExpression( let authExp: Expression = getConfigExpression(
snapshot, snapshot,
"cwmp.connectionRequestAuth" "cwmp.connectionRequestAuth",
); );
if (authExp === undefined) { if (authExp === undefined) {
@@ -176,10 +176,10 @@ export async function connectionRequest(
authExp, authExp,
UDP_CONNECTION_REQUEST_PORT, UDP_CONNECTION_REQUEST_PORT,
debug, debug,
deviceId deviceId,
).then( ).then(
() => true, () => true,
() => false () => false,
); );
} catch (err) { } catch (err) {
// Ignore invalid address // Ignore invalid address
@@ -192,7 +192,7 @@ export async function connectionRequest(
CONNECTION_REQUEST_ALLOW_BASIC_AUTH, CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT, CONNECTION_REQUEST_TIMEOUT,
debug, debug,
deviceId deviceId,
); );
if (await udpProm) return ""; if (await udpProm) return "";
@@ -203,12 +203,12 @@ export async function connectionRequest(
export async function awaitSessionStart( export async function awaitSessionStart(
deviceId: string, deviceId: string,
lastInform: number, lastInform: number,
timeout: number timeout: number,
): Promise<boolean> { ): Promise<boolean> {
const now = Date.now(); const now = Date.now();
const device = await collections.devices.findOne( const device = await collections.devices.findOne(
{ _id: deviceId }, { _id: deviceId },
{ projection: { _lastInform: 1 } } { projection: { _lastInform: 1 } },
); );
const li = (device["_lastInform"] as Date).getTime(); const li = (device["_lastInform"] as Date).getTime();
if (li > lastInform) return true; if (li > lastInform) return true;
@@ -222,7 +222,7 @@ export async function awaitSessionStart(
export async function awaitSessionEnd( export async function awaitSessionEnd(
deviceId: string, deviceId: string,
timeout: number timeout: number,
): Promise<boolean> { ): Promise<boolean> {
const now = Date.now(); const now = Date.now();
const token = await getToken(`cwmp_session_${deviceId}`); const token = await getToken(`cwmp_session_${deviceId}`);
@@ -317,8 +317,8 @@ function sanitizeTask(task): void {
!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),
) ),
) )
) )
throw new Error("Invalid 'provisions' property"); throw new Error("Invalid 'provisions' property");
@@ -395,7 +395,7 @@ export async function deleteFault(id: string): Promise<void> {
export async function deleteResource( export async function deleteResource(
resource: string, resource: string,
id: string id: string,
): Promise<void> { ): Promise<void> {
if (resource === "devices") { if (resource === "devices") {
await deleteDevice(id); await deleteDevice(id);
@@ -434,7 +434,7 @@ export async function deleteResource(
export async function putResource( export async function putResource(
resource: string, resource: string,
id: string, id: string,
data: any data: any,
): Promise<void> { ): Promise<void> {
if (resource === "presets") { if (resource === "presets") {
await putPreset(id, data); await putPreset(id, data);
@@ -467,7 +467,7 @@ export async function putResource(
export function authLocal( export function authLocal(
snapshot: string, snapshot: string,
username: string, username: string,
password: string password: string,
): Promise<boolean> { ): Promise<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const users = getUsers(snapshot); const users = getUsers(snapshot);
+5 -5
View File
@@ -63,7 +63,7 @@ export function parseAuthorizationHeader(authHeader: string): {
// Inspired by https://github.com/jshttp/basic-auth // Inspired by https://github.com/jshttp/basic-auth
const USER_PASS_REGEX = /^([^:]*):(.*)$/; const USER_PASS_REGEX = /^([^:]*):(.*)$/;
const creds = USER_PASS_REGEX.exec( const creds = USER_PASS_REGEX.exec(
Buffer.from(authHeader.slice(method.length + 1), "base64").toString() Buffer.from(authHeader.slice(method.length + 1), "base64").toString(),
); );
if (!creds) throw new Error("Unable to parse auth header"); if (!creds) throw new Error("Unable to parse auth header");
@@ -77,7 +77,7 @@ export function parseAuthorizationHeader(authHeader: string): {
} }
export function parseWwwAuthenticateHeader( export function parseWwwAuthenticateHeader(
authHeader: string authHeader: string,
): Record<string, string> { ): Record<string, string> {
authHeader = authHeader.trim(); authHeader = authHeader.trim();
const method = authHeader.split(" ", 1)[0]; const method = authHeader.split(" ", 1)[0];
@@ -100,7 +100,7 @@ export function digest(
qop?: string | Buffer, qop?: string | Buffer,
body?: string | Buffer, body?: string | Buffer,
cnonce?: string | Buffer, cnonce?: string | Buffer,
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);
@@ -141,7 +141,7 @@ export function solveDigest(
uri: string | Buffer, uri: string | Buffer,
httpMethod: string | Buffer, httpMethod: string | Buffer,
body: string | Buffer, body: string | Buffer,
authHeader: Record<string, string> authHeader: Record<string, string>,
): string { ): string {
const cnonce = randomBytes(8).toString("hex"); const cnonce = randomBytes(8).toString("hex");
const nc = "00000001"; const nc = "00000001";
@@ -163,7 +163,7 @@ export function solveDigest(
qop, qop,
body, body,
cnonce, cnonce,
nc nc,
); );
let authString = `Digest username="${username}"`; let authString = `Digest username="${username}"`;
+3 -3
View File
@@ -35,16 +35,16 @@ export async function del(key: string): Promise<void> {
export async function set( export async function set(
key: string, key: string,
value: string, value: string,
ttl: number = MAX_CACHE_TTL ttl: number = MAX_CACHE_TTL,
): Promise<void> { ): Promise<void> {
const timestamp = new Date(); const timestamp = new Date();
const expire = new Date( const expire = new Date(
timestamp.getTime() + CLOCK_SKEW_TOLERANCE + ttl * 1000 timestamp.getTime() + CLOCK_SKEW_TOLERANCE + ttl * 1000,
); );
await collections.cache.replaceOne( await collections.cache.replaceOne(
{ _id: key }, { _id: key },
{ value, expire, timestamp }, { value, expire, timestamp },
{ upsert: true } { upsert: true },
); );
} }
+1 -1
View File
@@ -92,7 +92,7 @@ function restartWorker(worker, code, signal): void {
export function start( export function start(
workerCount: number, workerCount: number,
servicePort: number, servicePort: number,
serviceAddress: string serviceAddress: string,
): void { ): void {
cluster.on("listening", (worker, address) => { cluster.on("listening", (worker, address) => {
if ( if (
+3 -3
View File
@@ -78,7 +78,7 @@ export default class Authorizer {
public getValidator( public getValidator(
resourceType: string, resourceType: string,
resource: unknown resource: unknown,
): (mutationType: string, mutation?: any, args?: any) => boolean { ): (mutationType: string, mutation?: any, args?: any) => boolean {
if (this.validatorCache.has(resource)) if (this.validatorCache.has(resource))
return this.validatorCache.get(resource); return this.validatorCache.get(resource);
@@ -99,7 +99,7 @@ export default class Authorizer {
const validator = ( const validator = (
mutationType: string, mutationType: string,
mutation: any, mutation: any,
any: any any: any,
): boolean => { ): boolean => {
if (!validators.length) return false; if (!validators.length) return false;
@@ -134,7 +134,7 @@ export default class Authorizer {
const res = evaluate( const res = evaluate(
validators.length > 1 ? ["OR", validators] : validators[0], validators.length > 1 ? ["OR", validators] : validators[0],
valueFunction, valueFunction,
Date.now() Date.now(),
); );
return !Array.isArray(res) && !!res; return !Array.isArray(res) && !!res;
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
export default function debounce<T>( export default function debounce<T>(
func: (args: T[]) => void, func: (args: T[]) => void,
timeout: number timeout: number,
): (arg: T) => void { ): (arg: T) => void {
let timer: ReturnType<typeof setTimeout>; let timer: ReturnType<typeof setTimeout>;
let args: T[] = []; let args: T[] = [];
+16 -16
View File
@@ -32,7 +32,7 @@ class Indeterminates {
public static multiply( public static multiply(
indeterminates1: Indeterminates, indeterminates1: Indeterminates,
indeterminates2: Indeterminates indeterminates2: Indeterminates,
): Indeterminates { ): Indeterminates {
const res = new Indeterminates(); const res = new Indeterminates();
res.sortedKeys = indeterminates1.sortedKeys.slice(); res.sortedKeys = indeterminates1.sortedKeys.slice();
@@ -108,7 +108,7 @@ class Polynomial {
const ts = terms const ts = terms
.slice() .slice()
.sort((a: Term, b: Term) => .sort((a: Term, b: Term) =>
Indeterminates.compare(a.indeterminates, b.indeterminates) Indeterminates.compare(a.indeterminates, b.indeterminates),
); );
for (let i = 1; i < ts.length; ++i) { for (let i = 1; i < ts.length; ++i) {
@@ -117,12 +117,12 @@ class Polynomial {
if (Indeterminates.compare(t1.indeterminates, t2.indeterminates) === 0) { if (Indeterminates.compare(t1.indeterminates, t2.indeterminates) === 0) {
const numerator = BI.add( const numerator = BI.add(
BI.mul(t1.coefficientNumerator, t2.coefficientDenominator), BI.mul(t1.coefficientNumerator, t2.coefficientDenominator),
BI.mul(t2.coefficientNumerator, t1.coefficientDenominator) BI.mul(t2.coefficientNumerator, t1.coefficientDenominator),
); );
const denominator = BI.mul( const denominator = BI.mul(
t1.coefficientDenominator, t1.coefficientDenominator,
t2.coefficientDenominator t2.coefficientDenominator,
); );
const gcd = findGcd(numerator, denominator); const gcd = findGcd(numerator, denominator);
@@ -163,7 +163,7 @@ class Polynomial {
denominator = BI.exp(TWO, BI.BigInt(frac.length)); denominator = BI.exp(TWO, BI.BigInt(frac.length));
numerator = BI.add( numerator = BI.add(
BI.mul(numerator, denominator), BI.mul(numerator, denominator),
BI.BigInt("0b" + frac) BI.BigInt("0b" + frac),
); );
} }
@@ -204,7 +204,7 @@ class Polynomial {
public add(rhs: Polynomial): Polynomial { public add(rhs: Polynomial): Polynomial {
return new Polynomial( return new Polynomial(
Polynomial.simplifyTerms(this.terms.concat(rhs.terms)) Polynomial.simplifyTerms(this.terms.concat(rhs.terms)),
); );
} }
@@ -219,18 +219,18 @@ class Polynomial {
for (const t2 of rhs.terms) { for (const t2 of rhs.terms) {
const numerator = BI.mul( const numerator = BI.mul(
t1.coefficientNumerator, t1.coefficientNumerator,
t2.coefficientNumerator t2.coefficientNumerator,
); );
const denominator = BI.mul( const denominator = BI.mul(
t1.coefficientDenominator, t1.coefficientDenominator,
t2.coefficientDenominator t2.coefficientDenominator,
); );
const gcd = findGcd(numerator, denominator); const gcd = findGcd(numerator, denominator);
terms.push({ terms.push({
indeterminates: Indeterminates.multiply( indeterminates: Indeterminates.multiply(
t1.indeterminates, t1.indeterminates,
t2.indeterminates t2.indeterminates,
), ),
coefficientNumerator: BI.div(numerator, gcd), coefficientNumerator: BI.div(numerator, gcd),
coefficientDenominator: BI.div(denominator, gcd), coefficientDenominator: BI.div(denominator, gcd),
@@ -339,7 +339,7 @@ function normalizeCallback(exp: Expression): Expression {
const e = (r[1] as Expression[]).slice(); const e = (r[1] as Expression[]).slice();
e[i] = t; e[i] = t;
return [and(w, r[0]), e] as [Expression, Expression]; return [and(w, r[0]), e] as [Expression, Expression];
}) }),
); );
if (!Array.isArray(w) && w) break; if (!Array.isArray(w) && w) break;
} }
@@ -373,7 +373,7 @@ function normalizeCallback(exp: Expression): Expression {
} }
return args.reduce( return args.reduce(
(previousValue, currentValue) => previousValue.add(currentValue), (previousValue, currentValue) => previousValue.add(currentValue),
ADDITIVE_IDENTITY ADDITIVE_IDENTITY,
) as unknown as Expression; ) as unknown as Expression;
} else if (op === "*") { } else if (op === "*") {
const args: Polynomial[] = []; const args: Polynomial[] = [];
@@ -384,7 +384,7 @@ function normalizeCallback(exp: Expression): Expression {
} }
return args.reduce( return args.reduce(
(previousValue, currentValue) => previousValue.multiply(currentValue), (previousValue, currentValue) => previousValue.multiply(currentValue),
MULTIPLICATIVE_IDENTITY MULTIPLICATIVE_IDENTITY,
) as unknown as Expression; ) as unknown as Expression;
} else if (op === "-") { } else if (op === "-") {
const args: Polynomial[] = []; const args: Polynomial[] = [];
@@ -394,7 +394,7 @@ function normalizeCallback(exp: Expression): Expression {
args.push(p); args.push(p);
} }
return args.reduce((previousValue, currentValue) => return args.reduce((previousValue, currentValue) =>
previousValue.subtract(currentValue) previousValue.subtract(currentValue),
) as unknown as Expression; ) as unknown as Expression;
} else if (op === "/") { } else if (op === "/") {
const args: Polynomial[] = []; const args: Polynomial[] = [];
@@ -404,7 +404,7 @@ function normalizeCallback(exp: Expression): Expression {
args.push(p); args.push(p);
} }
return args.reduce((previousValue, currentValue) => return args.reduce((previousValue, currentValue) =>
previousValue.divide(currentValue) previousValue.divide(currentValue),
) as unknown as Expression; ) as unknown as Expression;
} else if (["=", "<>", ">", ">=", "<", "<="].includes(op)) { } else if (["=", "<>", ">", ">=", "<", "<="].includes(op)) {
if (exp[1] == null || exp[2] == null) return null; if (exp[1] == null || exp[2] == null) return null;
@@ -471,7 +471,7 @@ function normalizeCallback(exp: Expression): Expression {
// Restore polynomial expressions // Restore polynomial expressions
exp = exp.map((e) => exp = exp.map((e) =>
e instanceof Polynomial ? JSON.parse(e.toString()) : e e instanceof Polynomial ? JSON.parse(e.toString()) : e,
); );
exp = evaluateCallback(exp); exp = evaluateCallback(exp);
@@ -485,7 +485,7 @@ export default function normalize(expr: Expression): Expression {
expr = JSON.parse(expr.toString()); expr = JSON.parse(expr.toString());
} else if (Array.isArray(expr) && expr[0] === "CASE") { } else if (Array.isArray(expr) && expr[0] === "CASE") {
expr = expr.map((e) => expr = expr.map((e) =>
e instanceof Polynomial ? JSON.parse(e.toString()) : e e instanceof Polynomial ? JSON.parse(e.toString()) : e,
); );
} }
return expr; return expr;
+36 -36
View File
@@ -57,7 +57,7 @@ export function map<T>(exp: Expression, callback: (e: Expression) => T): T {
export async function mapAsync( export async function mapAsync(
exp: Expression, exp: Expression,
callback: (e: Expression) => Promise<Expression> callback: (e: Expression) => Promise<Expression>,
): Promise<Expression> { ): Promise<Expression> {
if (!Array.isArray(exp)) return callback(exp); if (!Array.isArray(exp)) return callback(exp);
@@ -75,7 +75,7 @@ export async function mapAsync(
function binaryLeft( function binaryLeft(
operatorsParser: parsimmon.Parser<string>, operatorsParser: parsimmon.Parser<string>,
nextParser: parsimmon.Parser<Expression> nextParser: parsimmon.Parser<Expression>,
): parsimmon.Parser<Expression> { ): parsimmon.Parser<Expression> {
return parsimmon.seqMap( return parsimmon.seqMap(
nextParser, nextParser,
@@ -85,7 +85,7 @@ function binaryLeft(
const [op, another] = ch; const [op, another] = ch;
if (Array.isArray(acc) && op === acc[0]) return acc.concat([another]); if (Array.isArray(acc) && op === acc[0]) return acc.concat([another]);
return [op, acc, another]; return [op, acc, another];
}, first) }, first),
); );
} }
@@ -98,7 +98,7 @@ const lang = parsimmon.createLanguage({
parsimmon.string("<="), parsimmon.string("<="),
parsimmon.string("="), parsimmon.string("="),
parsimmon.string(">"), parsimmon.string(">"),
parsimmon.string("<") parsimmon.string("<"),
) )
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
}, },
@@ -109,7 +109,7 @@ const lang = parsimmon.createLanguage({
parsimmon parsimmon
.regexp(/not\s+like/i) .regexp(/not\s+like/i)
.result("NOT LIKE") .result("NOT LIKE")
.desc("NOT LIKE") .desc("NOT LIKE"),
) )
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
@@ -124,7 +124,7 @@ const lang = parsimmon.createLanguage({
parsimmon parsimmon
.regexp(/is\s+not\s+null/i) .regexp(/is\s+not\s+null/i)
.result("IS NOT NULL") .result("IS NOT NULL")
.desc("IS NOT NULL") .desc("IS NOT NULL"),
) )
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
.skip(parsimmon.optWhitespace); .skip(parsimmon.optWhitespace);
@@ -159,8 +159,8 @@ const lang = parsimmon.createLanguage({
parsimmon.regexp(/[a-zA-Z0-9_.*-]+/), parsimmon.regexp(/[a-zA-Z0-9_.*-]+/),
r.Expression.wrap( r.Expression.wrap(
parsimmon.string("{").skip(parsimmon.optWhitespace), parsimmon.string("{").skip(parsimmon.optWhitespace),
parsimmon.string("}") parsimmon.string("}"),
) ),
) )
.atLeast(1) .atLeast(1)
.map((x) => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]]) .map((x) => ["PARAM", x.length > 1 ? ["||"].concat(x) : x[0]])
@@ -194,7 +194,7 @@ const lang = parsimmon.createLanguage({
return parsimmon return parsimmon
.alt( .alt(
parsimmon.regexp(/true/i).result(true).desc("TRUE"), parsimmon.regexp(/true/i).result(true).desc("TRUE"),
parsimmon.regexp(/false/i).result(false).desc("FALSE") 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);
@@ -215,9 +215,9 @@ const lang = parsimmon.createLanguage({
.desc("function"), .desc("function"),
r.ExpressionList.wrap( r.ExpressionList.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) { WhenPair: function (r) {
@@ -233,7 +233,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)
.desc("THEN") .desc("THEN")
.then(r.Expression) .then(r.Expression),
); );
}, },
CaseStatement: function (r) { CaseStatement: function (r) {
@@ -256,10 +256,10 @@ const lang = parsimmon.createLanguage({
.skip( .skip(
parsimmon parsimmon
.regex(/end/i) .regex(/end/i)
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)) .notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/)),
) )
.skip(parsimmon.optWhitespace), .skip(parsimmon.optWhitespace),
(...arr) => arr.flat(2) (...arr) => arr.flat(2),
); );
}, },
Value: function (r) { Value: function (r) {
@@ -270,7 +270,7 @@ const lang = parsimmon.createLanguage({
r.StringValueSql, r.StringValueSql,
r.StringValueJs, r.StringValueJs,
r.FuncValue, r.FuncValue,
r.CaseStatement r.CaseStatement,
); );
}, },
ValueExpression: function (r) { ValueExpression: function (r) {
@@ -285,7 +285,7 @@ const lang = parsimmon.createLanguage({
.alt( .alt(
parsimmon.string("*"), parsimmon.string("*"),
parsimmon.string("/"), parsimmon.string("/"),
parsimmon.string("%") parsimmon.string("%"),
) )
.skip(parsimmon.optWhitespace), .skip(parsimmon.optWhitespace),
parsimmon.alt( parsimmon.alt(
@@ -293,11 +293,11 @@ const lang = parsimmon.createLanguage({
r.Parameter, r.Parameter,
r.Expression.wrap( r.Expression.wrap(
parsimmon.string("(").skip(parsimmon.optWhitespace), parsimmon.string("(").skip(parsimmon.optWhitespace),
parsimmon.string(")").skip(parsimmon.optWhitespace) parsimmon.string(")").skip(parsimmon.optWhitespace),
) ),
) ),
) ),
) ),
); );
}, },
Comparison: function (r) { Comparison: function (r) {
@@ -307,7 +307,7 @@ const lang = parsimmon.createLanguage({
r.ValueExpression, r.ValueExpression,
r.ComparisonOperator, r.ComparisonOperator,
r.ValueExpression, r.ValueExpression,
(p, o, v) => [o, p, v] (p, o, v) => [o, p, v],
), ),
parsimmon.seqMap( parsimmon.seqMap(
r.ValueExpression, r.ValueExpression,
@@ -317,28 +317,28 @@ const lang = parsimmon.createLanguage({
.regexp(/escape/i) .regexp(/escape/i)
.result("ESCAPE") .result("ESCAPE")
.skip(parsimmon.whitespace) .skip(parsimmon.whitespace)
.desc("ESCAPE") .desc("ESCAPE"),
), ),
r.ValueExpression, r.ValueExpression,
(a, b, c, d) => [b, a, c, d] (a, b, c, d) => [b, a, c, d],
), ),
parsimmon.seqMap( parsimmon.seqMap(
r.ValueExpression, r.ValueExpression,
r.LikeOperator, r.LikeOperator,
r.ValueExpression, r.ValueExpression,
(a, b, c) => [b, a, c] (a, b, c) => [b, a, c],
) ),
); );
}, },
ExpressionList: function (r) { ExpressionList: function (r) {
return r.Expression.sepBy( return r.Expression.sepBy(
parsimmon.string(",").skip(parsimmon.optWhitespace) parsimmon.string(",").skip(parsimmon.optWhitespace),
); );
}, },
Expression: function (r) { Expression: function (r) {
function unary( function unary(
operatorsParser: parsimmon.Parser<string>, operatorsParser: parsimmon.Parser<string>,
nextParser: parsimmon.Parser<Expression> nextParser: parsimmon.Parser<Expression>,
): parsimmon.Parser<Expression> { ): parsimmon.Parser<Expression> {
return parsimmon.seq(operatorsParser, nextParser).or(nextParser); return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
} }
@@ -347,8 +347,8 @@ const lang = parsimmon.createLanguage({
r.OrOperator, r.OrOperator,
binaryLeft( binaryLeft(
r.AndOperator, r.AndOperator,
unary(r.NotOperator, r.Comparison.or(r.ValueExpression)) unary(r.NotOperator, r.Comparison.or(r.ValueExpression)),
) ),
).trim(parsimmon.optWhitespace); ).trim(parsimmon.optWhitespace);
}, },
}); });
@@ -400,7 +400,7 @@ export function stringify(exp: Expression, level = 0): string {
`${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") {
if (typeof exp[1] === "string") { if (typeof exp[1] === "string") {
@@ -413,7 +413,7 @@ export function stringify(exp: Expression, level = 0): string {
if (typeof p === "string") return p; if (typeof p === "string") return p;
else return `{${stringify(p)}}`; else return `{${stringify(p)}}`;
}) })
.join("") .join(""),
); );
} else { } else {
return wrap(`{${stringify(exp[1])}}`); return wrap(`{${stringify(exp[1])}}`);
@@ -425,15 +425,15 @@ export function stringify(exp: Expression, level = 0): string {
return wrap( return wrap(
`${stringify(exp[1], opLevels[op])} ${op} ${stringify( `${stringify(exp[1], opLevels[op])} ${op} ${stringify(
exp[2], exp[2],
opLevels[op] opLevels[op],
)} ESCAPE ${stringify(exp[3], opLevels[op])}` )} ESCAPE ${stringify(exp[3], opLevels[op])}`,
); );
} else { } else {
return wrap( return wrap(
`${stringify(exp[1], opLevels[op])} ${op} ${stringify( `${stringify(exp[1], opLevels[op])} ${op} ${stringify(
exp[2], exp[2],
opLevels[op] opLevels[op],
)}` )}`,
); );
} }
} else if (op === "CASE") { } else if (op === "CASE") {
+26 -22
View File
@@ -7,7 +7,7 @@ type Minterm = number[];
export abstract class SynthContextBase< export abstract class SynthContextBase<
T extends { toString: () => string } = unknown, T extends { toString: () => string } = unknown,
U = unknown U = unknown,
> { > {
public variables = new Map<string, number>(); public variables = new Map<string, number>();
protected clauses = new Map<number, U>(); protected clauses = new Map<number, U>();
@@ -88,7 +88,7 @@ export abstract class Clause {
isNullable(c: Clause.IsNull): boolean { isNullable(c: Clause.IsNull): boolean {
if (!this._isNullable) { if (!this._isNullable) {
this._isNullable = new Set( this._isNullable = new Set(
[...this.getNullables()].map((n) => n.toString()) [...this.getNullables()].map((n) => n.toString()),
); );
} }
return this._isNullable.has(c.toString()); return this._isNullable.has(c.toString());
@@ -243,14 +243,14 @@ export namespace Clause {
const w = this.clauses[i].true(context); const w = this.clauses[i].true(context);
const t = this.clauses[i + 1].true(context); const t = this.clauses[i + 1].true(context);
minterms.push( minterms.push(
...complement([...cumulative, ...complement(w), ...complement(t)]) ...complement([...cumulative, ...complement(w), ...complement(t)]),
); );
if (i < this.clauses.length - 2) { if (i < this.clauses.length - 2) {
cumulative.push( cumulative.push(
...complement([ ...complement([
...this.clauses[i].false(context), ...this.clauses[i].false(context),
...this.clauses[i].null(context), ...this.clauses[i].null(context),
]) ]),
); );
} }
} }
@@ -263,14 +263,14 @@ export namespace Clause {
const w = this.clauses[i].true(context); const w = this.clauses[i].true(context);
const t = this.clauses[i + 1].false(context); const t = this.clauses[i + 1].false(context);
minterms.push( minterms.push(
...complement([...cumulative, ...complement(w), ...complement(t)]) ...complement([...cumulative, ...complement(w), ...complement(t)]),
); );
if (i < this.clauses.length - 2) { if (i < this.clauses.length - 2) {
cumulative.push( cumulative.push(
...complement([ ...complement([
...this.clauses[i].false(context), ...this.clauses[i].false(context),
...this.clauses[i].null(context), ...this.clauses[i].null(context),
]) ]),
); );
} }
} }
@@ -283,13 +283,13 @@ export namespace Clause {
const w = this.clauses[i].true(context); const w = this.clauses[i].true(context);
const t = this.clauses[i + 1].null(context); const t = this.clauses[i + 1].null(context);
minterms.push( minterms.push(
...complement([...cumulative, ...complement(w), ...complement(t)]) ...complement([...cumulative, ...complement(w), ...complement(t)]),
); );
cumulative.push( cumulative.push(
...complement([ ...complement([
...this.clauses[i].false(context), ...this.clauses[i].false(context),
...this.clauses[i].null(context), ...this.clauses[i].null(context),
]) ]),
); );
} }
minterms.push(...complement([...cumulative])); minterms.push(...complement([...cumulative]));
@@ -313,7 +313,7 @@ export namespace Clause {
minterms.push(...cases.pop().when); minterms.push(...cases.pop().when);
minterms = context.minimize( minterms = context.minimize(
minterms, minterms,
cases.flatMap((c) => c.when) cases.flatMap((c) => c.when),
); );
if (!minterms.length) continue; if (!minterms.length) continue;
cases.push({ when: minterms, then }); cases.push({ when: minterms, then });
@@ -395,7 +395,7 @@ export namespace Clause {
constructor( constructor(
public lhs: Clause, public lhs: Clause,
public op: ">" | "<" | "=", public op: ">" | "<" | "=",
public rhs: boolean | number | string public rhs: boolean | number | string,
) { ) {
super(); super();
} }
@@ -425,7 +425,11 @@ export namespace Clause {
public readonly pattern: string[]; public readonly pattern: string[];
public readonly lhs: Clause; public readonly lhs: Clause;
constructor(lhs: Clause, public rhs: string, public esc?: string) { constructor(
lhs: Clause,
public rhs: string,
public esc?: string,
) {
super(); super();
const exp = lhs.expression(); const exp = lhs.expression();
let caseSensitive = true; let caseSensitive = true;
@@ -515,7 +519,7 @@ export namespace Clause {
clause = new Clause.Compare( clause = new Clause.Compare(
e[1], e[1],
op, op,
rhs as boolean | number | string rhs as boolean | number | string,
); );
} }
} }
@@ -535,7 +539,7 @@ export namespace Clause {
function groupBy<T, K>( function groupBy<T, K>(
input: T[], input: T[],
callback: (item: T) => K callback: (item: T) => K,
): Iterable<[K, T[]]> { ): Iterable<[K, T[]]> {
const groups = new Map<K, T[]>(); const groups = new Map<K, T[]>();
for (const item of input) { for (const item of input) {
@@ -567,12 +571,12 @@ export class SynthContext extends SynthContextBase<Clause, Clause> {
const allClauses = [...whitelist].map((v) => this.getClause(v)); const allClauses = [...whitelist].map((v) => this.getClause(v));
const comparisons = allClauses.filter( const comparisons = allClauses.filter(
(c) => c instanceof Clause.Compare (c) => c instanceof Clause.Compare,
) as Clause.Compare[]; ) as Clause.Compare[];
// Comparisons // Comparisons
for (const [, clauses] of groupBy(comparisons, (c) => for (const [, clauses] of groupBy(comparisons, (c) =>
JSON.stringify(c.lhs) JSON.stringify(c.lhs),
)) { )) {
const lhs = clauses[0].lhs; const lhs = clauses[0].lhs;
const values = new Set(clauses.map((c) => c.rhs)); const values = new Set(clauses.map((c) => c.rhs));
@@ -600,13 +604,13 @@ export class SynthContext extends SynthContextBase<Clause, Clause> {
for (let j = 0; j < i; j++) { for (let j = 0; j < i; j++) {
const eq2 = this.getVar( const eq2 = this.getVar(
new Clause.Compare(lhs, "=", valuesSorted[j]) new Clause.Compare(lhs, "=", valuesSorted[j]),
); );
const gt2 = this.getVar( const gt2 = this.getVar(
new Clause.Compare(lhs, ">", valuesSorted[j]) new Clause.Compare(lhs, ">", valuesSorted[j]),
); );
const lt2 = this.getVar( const lt2 = this.getVar(
new Clause.Compare(lhs, "<", valuesSorted[j]) new Clause.Compare(lhs, "<", valuesSorted[j]),
); );
// This is the minimum clauses required if all relavent vars // This is the minimum clauses required if all relavent vars
@@ -631,7 +635,7 @@ export class SynthContext extends SynthContextBase<Clause, Clause> {
// LIKE // LIKE
const likes = allClauses.filter( const likes = allClauses.filter(
(c) => c instanceof Clause.Like (c) => c instanceof Clause.Like,
) as Clause.Like[]; ) as Clause.Like[];
for (const [, clauses] of groupBy(likes, (c) => JSON.stringify(c.lhs))) { for (const [, clauses] of groupBy(likes, (c) => JSON.stringify(c.lhs))) {
@@ -734,7 +738,7 @@ export class SynthContext extends SynthContextBase<Clause, Clause> {
for (const [k, v] of merged) { for (const [k, v] of merged) {
if (v === 0b1010) continue loop; if (v === 0b1010) continue loop;
const isNullVars = [...this.clauses.get(k).getNullables()].map((c) => const isNullVars = [...this.clauses.get(k).getNullables()].map((c) =>
this.getVar(c) this.getVar(c),
); );
const t = k << 2; const t = k << 2;
if (v === 0b0101) { if (v === 0b0101) {
@@ -858,7 +862,7 @@ export function minimize(expr: Expression, boolean = false): Expression {
export function unionDiff( export function unionDiff(
expr1: Expression, expr1: Expression,
expr2: Expression expr2: Expression,
): [Expression, Expression] { ): [Expression, Expression] {
expr2 = normalize(expr2); expr2 = normalize(expr2);
@@ -887,7 +891,7 @@ export function unionDiff(
complement([ complement([
...complement([...expr1NullMinterms, ...expr1FalseMinterms]), ...complement([...expr1NullMinterms, ...expr1FalseMinterms]),
...complement(expr2Minterms), ...complement(expr2Minterms),
]) ]),
); );
return [context.toExpression(union), context.toExpression(diff)]; return [context.toExpression(union), context.toExpression(diff)];
+7 -7
View File
@@ -74,7 +74,7 @@ export function likePatternToRegExp(pat: string, esc = "", flags = ""): RegExp {
function compare( function compare(
a: boolean | number | string, a: boolean | number | string,
b: boolean | number | string b: boolean | number | string,
): number { ): number {
if (typeof a === "boolean") a = +a; if (typeof a === "boolean") a = +a;
if (typeof b === "boolean") b = +b; if (typeof b === "boolean") b = +b;
@@ -287,19 +287,19 @@ export function evaluate(
exp: Expression, exp: Expression,
obj: Record<string, unknown> | ((e: string) => Expression), obj: Record<string, unknown> | ((e: string) => Expression),
now: number, now: number,
cb?: (e: Expression) => Expression cb?: (e: Expression) => Expression,
): string | number | boolean | null; ): string | number | boolean | null;
export function evaluate( export function evaluate(
exp: Expression, exp: Expression,
obj?: Record<string, unknown> | ((e: string) => Expression), obj?: Record<string, unknown> | ((e: string) => Expression),
now?: number, now?: number,
cb?: (e: Expression) => Expression cb?: (e: Expression) => Expression,
): Expression; ): Expression;
export function evaluate( export function evaluate(
exp: Expression, exp: Expression,
obj?: Record<string, unknown> | ((e: string) => Expression), obj?: Record<string, unknown> | ((e: string) => Expression),
now?: number, now?: number,
cb?: (e: Expression) => Expression cb?: (e: Expression) => Expression,
): Expression { ): Expression {
return map(exp, (e) => { return map(exp, (e) => {
if (cb) e = cb(e); if (cb) e = cb(e);
@@ -327,19 +327,19 @@ export async function evaluateAsync(
exp: Expression, exp: Expression,
obj: Record<string, unknown>, obj: Record<string, unknown>,
now: number, now: number,
cb?: (e: Expression) => Promise<Expression> cb?: (e: Expression) => Promise<Expression>,
): Promise<string | number | boolean | null>; ): Promise<string | number | boolean | null>;
export async function evaluateAsync( export async function evaluateAsync(
exp: Expression, exp: Expression,
obj?: Record<string, unknown>, obj?: Record<string, unknown>,
now?: number, now?: number,
cb?: (e: Expression) => Promise<Expression> cb?: (e: Expression) => Promise<Expression>,
): Promise<Expression>; ): Promise<Expression>;
export async function evaluateAsync( export async function evaluateAsync(
exp: Expression, exp: Expression,
obj?: Record<string, unknown>, obj?: Record<string, unknown>,
now?: number, now?: number,
cb?: (e: Expression) => Promise<Expression> cb?: (e: Expression) => Promise<Expression>,
): Promise<Expression> { ): Promise<Expression> {
return mapAsync(exp, async (e) => { return mapAsync(exp, async (e) => {
if (cb) e = await cb(e); if (cb) e = await cb(e);
+2 -2
View File
@@ -75,7 +75,7 @@ export default class PathSet {
path: Path, path: Path,
superset = false, superset = false,
subset = false, subset = false,
depth: number = path.length depth: number = path.length,
): Path[] { ): Path[] {
if (path.alias) throw new Error("PathSet does not support aliased paths"); if (path.alias) throw new Error("PathSet does not support aliased paths");
@@ -115,7 +115,7 @@ export default class PathSet {
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),
); );
} }
} }
+5 -5
View File
@@ -57,7 +57,7 @@ export default class Path {
protected static parseAliasValue( protected static parseAliasValue(
pattern: string, pattern: string,
index: number index: number,
): { index: number; value: string } { ): { index: number; value: string } {
let i = index; let i = index;
while (charCodeAt(pattern, i) === CHAR_SPACE) ++i; while (charCodeAt(pattern, i) === CHAR_SPACE) ++i;
@@ -89,7 +89,7 @@ export default class Path {
protected static parseAliasPath( protected static parseAliasPath(
pattern: string, pattern: string,
index: number index: number,
): { index: number; path: Path } { ): { index: number; path: Path } {
let i = index; let i = index;
while (charCodeAt(pattern, i) === CHAR_SPACE) ++i; while (charCodeAt(pattern, i) === CHAR_SPACE) ++i;
@@ -103,7 +103,7 @@ export default class Path {
protected static parseAlias( protected static parseAlias(
pattern: string, pattern: string,
index: number index: number,
): { index: number; alias: Alias } { ): { index: number; alias: Alias } {
const alias: Alias = []; const alias: Alias = [];
let i = index; let i = index;
@@ -136,7 +136,7 @@ export default class Path {
protected static parsePath( protected static parsePath(
pattern: string, pattern: string,
index: number index: number,
): { index: number; segments: Segments } { ): { index: number; segments: Segments } {
const segments = []; const segments = [];
@@ -173,7 +173,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 === "*") {
+6 -6
View File
@@ -15,7 +15,7 @@ const STRING_RESERVED = new Set([
function isPrintable(str: string): boolean { function isPrintable(str: string): boolean {
return !/[^\t\n\x20-\x7e\x85\u{a0}-\u{d7ff}\u{e000}-\u{fffd}\u{10000}-\u{10ffff}]/u.test( return !/[^\t\n\x20-\x7e\x85\u{a0}-\u{d7ff}\u{e000}-\u{fffd}\u{10000}-\u{10ffff}]/u.test(
str str,
); );
} }
@@ -99,7 +99,7 @@ function stringifyString(str: string, res: string[], prefix1, prefix2): void {
if (!isFolded) { if (!isFolded) {
res.push( res.push(
`${prefix1}|${idt}${chmp}`, `${prefix1}|${idt}${chmp}`,
...lines.map((l) => (l ? prefix2 + l : l)) ...lines.map((l) => (l ? prefix2 + l : l)),
); );
return; return;
} }
@@ -129,7 +129,7 @@ function stringifyAny(
obj: unknown, obj: unknown,
res: string[], res: string[],
prefix1 = "", prefix1 = "",
prefix2 = "" prefix2 = "",
): void { ): void {
if (obj == null) { if (obj == null) {
res.push(`${prefix1}null`); res.push(`${prefix1}null`);
@@ -183,7 +183,7 @@ function stringifyAny(
entries[0][1], entries[0][1],
res, res,
prefix1 + `${stringifyKey(entries[0][0])}: `, prefix1 + `${stringifyKey(entries[0][0])}: `,
prefix2 + INDENTATION prefix2 + INDENTATION,
); );
prefix1 = prefix2; prefix1 = prefix2;
prefix2 = prefix2 + INDENTATION; prefix2 = prefix2 + INDENTATION;
@@ -192,7 +192,7 @@ function stringifyAny(
entries[i][1], entries[i][1],
res, res,
prefix1 + `${stringifyKey(entries[i][0])}: `, prefix1 + `${stringifyKey(entries[i][0])}: `,
prefix2 prefix2,
); );
} }
} else { } else {
@@ -204,7 +204,7 @@ function stringifyAny(
entries[i][1], entries[i][1],
res, res,
prefix1 + `${stringifyKey(entries[i][0])}: `, prefix1 + `${stringifyKey(entries[i][0])}: `,
prefix2 prefix2,
); );
} }
} }
+2 -2
View File
@@ -245,7 +245,7 @@ if (fsHostname) {
const FS_SSL = !!allConfig["FS_SSL_CERT"]; const FS_SSL = !!allConfig["FS_SSL_CERT"];
setConfig( setConfig(
"FS_URL_PREFIX", "FS_URL_PREFIX",
(FS_SSL ? "https" : "http") + `://${fsHostname}:${FS_PORT}/` (FS_SSL ? "https" : "http") + `://${fsHostname}:${FS_PORT}/`,
); );
} }
@@ -255,7 +255,7 @@ for (const [k, v] of Object.entries(options))
export function get( export function get(
optionName: string, optionName: string,
deviceId?: string deviceId?: string,
): string | number | boolean { ): string | number | boolean {
if (!deviceId) return allConfig[optionName]; if (!deviceId) return allConfig[optionName];
+9 -10
View File
@@ -19,7 +19,6 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
import * as dgram from "dgram"; import * as dgram from "dgram";
import { URL } from "url";
import * as http from "http"; import * as http from "http";
import { evaluateAsync } from "./common/expression/util"; import { evaluateAsync } from "./common/expression/util";
import { Expression } from "./types"; import { Expression } from "./types";
@@ -29,7 +28,7 @@ import * as debug from "./debug";
async function extractAuth( async function extractAuth(
exp: Expression, exp: Expression,
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(
@@ -55,7 +54,7 @@ async function extractAuth(
} }
} }
return e; return e;
} },
); );
return [username, password, _exp]; return [username, password, _exp];
} }
@@ -64,7 +63,7 @@ function httpGet(
url: URL, url: URL,
options: http.RequestOptions, options: http.RequestOptions,
_debug: boolean, _debug: boolean,
deviceId: string deviceId: string,
): Promise<{ statusCode: number; headers: http.IncomingHttpHeaders }> { ): Promise<{ statusCode: number; headers: http.IncomingHttpHeaders }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = http const req = http
@@ -94,7 +93,7 @@ export async function httpConnectionRequest(
allowBasicAuth: boolean, allowBasicAuth: boolean,
timeout: number, timeout: number,
_debug: boolean, _debug: boolean,
deviceId: string deviceId: string,
): Promise<string> { ): Promise<string> {
const url = new URL(address); const url = new URL(address);
if (url.protocol !== "http:") if (url.protocol !== "http:")
@@ -120,7 +119,7 @@ export async function httpConnectionRequest(
Authorization: auth.basic(username || "", password || ""), Authorization: auth.basic(username || "", password || ""),
}, },
}, },
options options,
); );
} else if (authHeader["method"] === "Digest") { } else if (authHeader["method"] === "Digest") {
opts = Object.assign( opts = Object.assign(
@@ -132,11 +131,11 @@ export async function httpConnectionRequest(
url.pathname + url.search, url.pathname + url.search,
"GET", "GET",
null, null,
authHeader authHeader,
), ),
}, },
}, },
options options,
); );
} else { } else {
return "Unrecognized auth method"; return "Unrecognized auth method";
@@ -171,7 +170,7 @@ export async function httpConnectionRequest(
if (res.statusCode === 401 && res.headers["www-authenticate"]) { if (res.statusCode === 401 && res.headers["www-authenticate"]) {
try { try {
authHeader = auth.parseWwwAuthenticateHeader( authHeader = auth.parseWwwAuthenticateHeader(
res.headers["www-authenticate"] res.headers["www-authenticate"],
); );
} catch (err) { } catch (err) {
return "Connection request error: Error parsing www-authenticate header"; return "Connection request error: Error parsing www-authenticate header";
@@ -191,7 +190,7 @@ export async function udpConnectionRequest(
authExp: Expression, authExp: Expression,
sourcePort = 0, sourcePort = 0,
_debug: boolean, _debug: boolean,
deviceId: string deviceId: string,
): Promise<void> { ): Promise<void> {
const now = Date.now(); const now = Date.now();
+63 -64
View File
@@ -89,11 +89,11 @@ const stats = {
async function authenticate( async function authenticate(
sessionContext: SessionContext, sessionContext: SessionContext,
body: string body: string,
): Promise<boolean> { ): Promise<boolean> {
const authExpression: Expression = localCache.getConfigExpression( const authExpression: Expression = localCache.getConfigExpression(
sessionContext.cacheSnapshot, sessionContext.cacheSnapshot,
"cwmp.auth" "cwmp.auth",
); );
if (authExpression == null) return true; if (authExpression == null) return true;
@@ -102,7 +102,7 @@ async function authenticate(
if (sessionContext.httpRequest.headers["authorization"]) { if (sessionContext.httpRequest.headers["authorization"]) {
try { try {
authentication = auth.parseAuthorizationHeader( authentication = auth.parseAuthorizationHeader(
sessionContext.httpRequest.headers["authorization"] sessionContext.httpRequest.headers["authorization"],
); );
} catch (err) { } catch (err) {
return false; return false;
@@ -159,7 +159,7 @@ async function authenticate(
authentication["qop"], authentication["qop"],
authentication["body"], authentication["body"],
authentication["cnonce"], authentication["cnonce"],
authentication["nc"] authentication["nc"],
); );
return expected === authentication["response"]; return expected === authentication["response"];
} }
@@ -168,7 +168,7 @@ async function authenticate(
} }
} }
return e; return e;
} },
); );
if (res && !Array.isArray(res)) return true; if (res && !Array.isArray(res)) return true;
@@ -179,7 +179,7 @@ async function authenticate(
async function writeResponse( async function writeResponse(
sessionContext: SessionContext, sessionContext: SessionContext,
res, res,
close = false close = false,
): Promise<void> { ): Promise<void> {
// Close connection after last request in session // Close connection after last request in session
if (close) res.headers["Connection"] = "close"; if (close) res.headers["Connection"] = "close";
@@ -231,7 +231,7 @@ async function writeResponse(
`cwmp_session_${sessionContext.deviceId}`, `cwmp_session_${sessionContext.deviceId}`,
sessionContext.timeout * 1000 + 15000, sessionContext.timeout * 1000 + 15000,
0, 0,
`cwmp_session_${sessionContext.sessionId}` `cwmp_session_${sessionContext.sessionId}`,
); );
if (!lockToken) throw new Error("Failed to extend lock"); if (!lockToken) throw new Error("Failed to extend lock");
} }
@@ -242,14 +242,14 @@ function recordFault(
sessionContext: SessionContext, sessionContext: SessionContext,
fault: Fault, fault: Fault,
provisions, provisions,
channels channels,
): void; ): void;
function recordFault(sessionContext: SessionContext, fault: Fault): void; function recordFault(sessionContext: SessionContext, fault: Fault): void;
function recordFault( function recordFault(
sessionContext: SessionContext, sessionContext: SessionContext,
fault: Fault, fault: Fault,
provisions?, provisions?,
channels? channels?,
): void { ): void {
if (!provisions) { if (!provisions) {
provisions = sessionContext.provisions; provisions = sessionContext.provisions;
@@ -267,7 +267,7 @@ function recordFault(
: []; : [];
faults[channel] = Object.assign( faults[channel] = Object.assign(
{ provisions: provs, timestamp: sessionContext.timestamp }, { provisions: provs, timestamp: sessionContext.timestamp },
fault fault,
) as SessionFault; ) as SessionFault;
if (channel.startsWith("task_")) { if (channel.startsWith("task_")) {
const taskId = channel.slice(5); const taskId = channel.slice(5);
@@ -314,11 +314,11 @@ function recordFault(
async function inform( async function inform(
sessionContext: SessionContext, sessionContext: SessionContext,
rpc: SoapMessage rpc: SoapMessage,
): Promise<{ code: number; headers: Record<string, string>; data: string }> { ): Promise<{ code: number; headers: Record<string, string>; data: string }> {
const acsResponse = await session.inform( const acsResponse = await session.inform(
sessionContext, sessionContext,
rpc.cpeRequest as InformRequest rpc.cpeRequest as InformRequest,
); );
const res = soap.response({ const res = soap.response({
@@ -332,13 +332,12 @@ async function inform(
"cwmp.cookiesPath", "cwmp.cookiesPath",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
(e) => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e),
); );
if (cookiesPath) { if (cookiesPath) {
res.headers[ res.headers["Set-Cookie"] =
"Set-Cookie" `session=${sessionContext.sessionId}; Path=${cookiesPath}`;
] = `session=${sessionContext.sessionId}; Path=${cookiesPath}`;
} else { } else {
res.headers["Set-Cookie"] = `session=${sessionContext.sessionId}`; res.headers["Set-Cookie"] = `session=${sessionContext.sessionId}`;
} }
@@ -349,7 +348,7 @@ async function inform(
async function transferComplete(sessionContext, rpc): Promise<void> { async function transferComplete(sessionContext, rpc): Promise<void> {
const { acsResponse, operation, fault } = await session.transferComplete( const { acsResponse, operation, fault } = await session.transferComplete(
sessionContext, sessionContext,
rpc.cpeRequest rpc.cpeRequest,
); );
if (!operation) { if (!operation) {
@@ -366,7 +365,7 @@ async function transferComplete(sessionContext, rpc): Promise<void> {
sessionContext, sessionContext,
fault, fault,
operation.provisions, operation.provisions,
operation.channels operation.channels,
); );
} }
@@ -424,7 +423,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
"cwmp.retryDelay", "cwmp.retryDelay",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
(e) => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e),
); );
if (sessionContext.faults) { if (sessionContext.faults) {
@@ -452,7 +451,8 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
const deviceEvents = {}; const deviceEvents = {};
for (const p of deviceData.paths.find(Path.parse("Events.*"), false, true)) { for (const p of deviceData.paths.find(Path.parse("Events.*"), false, true)) {
const attrs = deviceData.attributes.get(p); const attrs = deviceData.attributes.get(p);
if (attrs?.value && attrs.value[1][0] >= sessionContext.timestamp) const t = attrs?.value[1][0] as number;
if (t >= sessionContext.timestamp)
deviceEvents[p.segments[1] as string] = true; deviceEvents[p.segments[1] as string] = true;
} }
@@ -479,7 +479,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
if (preset.schedule?.schedule) { if (preset.schedule?.schedule) {
const r = scheduling.cron( const r = scheduling.cron(
sessionContext.timestamp, sessionContext.timestamp,
preset.schedule.schedule preset.schedule.schedule,
); );
if (!(r[0] + preset.schedule.duration > sessionContext.timestamp)) if (!(r[0] + preset.schedule.duration > sessionContext.timestamp))
continue; continue;
@@ -487,7 +487,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
filteredPresets.push(preset); filteredPresets.push(preset);
for (const k of extractParams( for (const k of extractParams(
evaluate(preset.precondition, null, sessionContext.timestamp) evaluate(preset.precondition, null, sessionContext.timestamp),
)) { )) {
// Mark channel in case of fault during fetching precondition // Mark channel in case of fault during fetching precondition
sessionContext.channels[preset.channel] = 0; sessionContext.channels[preset.channel] = 0;
@@ -496,7 +496,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
for (const prov of preset.provisions) { for (const prov of preset.provisions) {
for (const arg of prov.slice(1)) { for (const arg of prov.slice(1)) {
for (const k of extractParams( for (const k of extractParams(
evaluate(arg, null, sessionContext.timestamp) evaluate(arg, null, sessionContext.timestamp),
)) { )) {
// Mark channel in case of fault during fetching precondition // Mark channel in case of fault during fetching precondition
sessionContext.channels[preset.channel] = 0; sessionContext.channels[preset.channel] = 0;
@@ -538,7 +538,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
for (const p of filteredPresets) { for (const p of filteredPresets) {
if ( if (
evaluate(p.precondition, {}, sessionContext.timestamp, (e) => evaluate(p.precondition, {}, sessionContext.timestamp, (e) =>
session.configContextCallback(sessionContext, e) session.configContextCallback(sessionContext, e),
) )
) { ) {
const provs = p.provisions.map((pp) => [ const provs = p.provisions.map((pp) => [
@@ -547,8 +547,8 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
.slice(1) .slice(1)
.map((arg) => .map((arg) =>
evaluate(arg, {}, sessionContext.timestamp, (e) => evaluate(arg, {}, sessionContext.timestamp, (e) =>
session.configContextCallback(sessionContext, e) session.configContextCallback(sessionContext, e),
) ),
), ),
]) as [string, ...Expression[]][]; ]) as [string, ...Expression[]][];
if (blackList[p.channel] === 2) { if (blackList[p.channel] === 2) {
@@ -562,7 +562,7 @@ async function applyPresets(sessionContext: SessionContext): Promise<void> {
} }
for (const [channel, provisions] of Object.entries( for (const [channel, provisions] of Object.entries(
appendProvisionsToFaults appendProvisionsToFaults,
)) { )) {
if ( if (
appendProvisions(sessionContext.faults[channel].provisions, provisions) appendProvisions(sessionContext.faults[channel].provisions, provisions)
@@ -686,7 +686,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);
@@ -747,7 +747,7 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
session.addProvisions( session.addProvisions(
sessionContext, sessionContext,
`task_${task._id}`, `task_${task._id}`,
task.provisions task.provisions,
); );
break; break;
default: default:
@@ -784,8 +784,8 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
sessionContext.deviceId, sessionContext.deviceId,
sessionContext.deviceData, sessionContext.deviceData,
sessionContext.new, sessionContext.new,
sessionContext.timestamp sessionContext.timestamp,
) ),
); );
if (sessionContext.operationsTouched) { if (sessionContext.operationsTouched) {
@@ -795,8 +795,8 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
saveOperation( saveOperation(
sessionContext.deviceId, sessionContext.deviceId,
k, k,
sessionContext.operations[k] sessionContext.operations[k],
) ),
); );
} else { } else {
promises.push(deleteOperation(sessionContext.deviceId, k)); promises.push(deleteOperation(sessionContext.deviceId, k));
@@ -806,7 +806,7 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
if (sessionContext.doneTasks?.length) { if (sessionContext.doneTasks?.length) {
promises.push( promises.push(
clearTasks(sessionContext.deviceId, sessionContext.doneTasks) clearTasks(sessionContext.deviceId, sessionContext.doneTasks),
); );
} }
@@ -815,7 +815,7 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
if (sessionContext.faults[k]) { if (sessionContext.faults[k]) {
sessionContext.faults[k].retries = sessionContext.retries[k]; sessionContext.faults[k].retries = sessionContext.retries[k];
promises.push( promises.push(
saveFault(sessionContext.deviceId, k, sessionContext.faults[k]) saveFault(sessionContext.deviceId, k, sessionContext.faults[k]),
); );
} else { } else {
promises.push(deleteFault(sessionContext.deviceId, k)); promises.push(deleteFault(sessionContext.deviceId, k));
@@ -826,7 +826,7 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
await Promise.all(promises); await Promise.all(promises);
await lock.releaseLock( await lock.releaseLock(
`cwmp_session_${sessionContext.deviceId}`, `cwmp_session_${sessionContext.deviceId}`,
`cwmp_session_${sessionContext.sessionId}` `cwmp_session_${sessionContext.sessionId}`,
); );
if (sessionContext.new) { if (sessionContext.new) {
logger.accessInfo({ logger.accessInfo({
@@ -839,7 +839,7 @@ async function endSession(sessionContext: SessionContext): Promise<void> {
async function sendAcsRequest( async function sendAcsRequest(
sessionContext: SessionContext, sessionContext: SessionContext,
id?: string, id?: string,
acsRequest?: AcsRequest acsRequest?: AcsRequest,
): Promise<void> { ): Promise<void> {
if (!acsRequest) if (!acsRequest)
return writeResponse(sessionContext, soap.response(null), true); return writeResponse(sessionContext, soap.response(null), true);
@@ -918,7 +918,7 @@ export async function onConnection(socket: Socket): Promise<void> {
await cache.set( await cache.set(
`session_${sessionContext.sessionId}`, `session_${sessionContext.sessionId}`,
await session.serialize(sessionContext), await session.serialize(sessionContext),
Math.ceil(timeout / 1000) + 3 Math.ceil(timeout / 1000) + 3,
); );
await setTimeoutPromise(timeout + 1000, false); await setTimeoutPromise(timeout + 1000, false);
@@ -946,7 +946,7 @@ export async function onClientError(err: Error, socket: Socket): Promise<void> {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return remoteAddress; return remoteAddress;
return e; return e;
} },
); );
if (debugEnabled) debug.clientError(remoteAddress, err); if (debugEnabled) debug.clientError(remoteAddress, err);
@@ -985,7 +985,7 @@ async function reportBadState(sessionContext: SessionContext): Promise<void> {
async function responseUnauthorized( async function responseUnauthorized(
sessionContext: SessionContext, sessionContext: SessionContext,
close: boolean close: boolean,
): Promise<void> { ): Promise<void> {
const resHeaders = {}; const resHeaders = {};
if (close) { if (close) {
@@ -1024,7 +1024,7 @@ async function processRequest(
sessionContext: SessionContext, sessionContext: SessionContext,
rpc: SoapMessage, rpc: SoapMessage,
parseWarnings: Record<string, unknown>[], parseWarnings: Record<string, unknown>[],
body: string body: string,
): Promise<void> { ): Promise<void> {
for (const w of parseWarnings) { for (const w of parseWarnings) {
w.sessionContext = sessionContext; w.sessionContext = sessionContext;
@@ -1042,7 +1042,7 @@ async function processRequest(
"cwmp.debug", "cwmp.debug",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
(e) => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e),
); );
if (!sessionContext.timeout) { if (!sessionContext.timeout) {
@@ -1051,7 +1051,7 @@ async function processRequest(
"cwmp.sessionTimeout", "cwmp.sessionTimeout",
{}, {},
sessionContext.timestamp, sessionContext.timestamp,
(e) => session.configContextCallback(sessionContext, e) (e) => session.configContextCallback(sessionContext, e),
); );
} }
@@ -1061,7 +1061,7 @@ async function processRequest(
debug.incomingHttpRequest( debug.incomingHttpRequest(
sessionContext.httpRequest, sessionContext.httpRequest,
sessionContext.deviceId, sessionContext.deviceId,
body body,
); );
} }
@@ -1080,7 +1080,7 @@ async function processRequest(
`cwmp_session_${sessionContext.deviceId}`, `cwmp_session_${sessionContext.deviceId}`,
sessionContext.timeout * 1000 + 15000, sessionContext.timeout * 1000 + 15000,
0, 0,
`cwmp_session_${sessionContext.sessionId}` `cwmp_session_${sessionContext.sessionId}`,
); );
if (!lockToken) { if (!lockToken) {
@@ -1092,14 +1092,14 @@ async function processRequest(
const _body = "CPE already in session"; const _body = "CPE already in session";
sessionContext.httpResponse.setHeader( sessionContext.httpResponse.setHeader(
"Content-Length", "Content-Length",
Buffer.byteLength(_body) Buffer.byteLength(_body),
); );
sessionContext.httpResponse.writeHead(400, { Connection: "close" }); sessionContext.httpResponse.writeHead(400, { Connection: "close" });
if (sessionContext.debug) { if (sessionContext.debug) {
debug.outgoingHttpResponse( debug.outgoingHttpResponse(
sessionContext.httpResponse, sessionContext.httpResponse,
sessionContext.deviceId, sessionContext.deviceId,
_body _body,
); );
} }
sessionContext.httpResponse.end(_body); sessionContext.httpResponse.end(_body);
@@ -1122,7 +1122,7 @@ async function processRequest(
debug.incomingHttpRequest( debug.incomingHttpRequest(
sessionContext.httpRequest, sessionContext.httpRequest,
sessionContext.deviceId, sessionContext.deviceId,
body body,
); );
} }
@@ -1180,7 +1180,7 @@ async function processRequest(
const fault = await session.rpcResponse( const fault = await session.rpcResponse(
sessionContext, sessionContext,
rpc.id, rpc.id,
rpc.cpeResponse rpc.cpeResponse,
); );
if (fault) { if (fault) {
recordFault(sessionContext, fault); recordFault(sessionContext, fault);
@@ -1242,9 +1242,8 @@ async function processRequest(
if (sessionContext.state !== 1) return reportBadState(sessionContext); if (sessionContext.state !== 1) return reportBadState(sessionContext);
sessionContext.state = 2; sessionContext.state = 2;
const { faults, operations } = await session.timeoutOperations( const { faults, operations } =
sessionContext await session.timeoutOperations(sessionContext);
);
for (const [i, f] of faults.entries()) { for (const [i, f] of faults.entries()) {
for (const [k, v] of Object.entries(operations[i].retries)) for (const [k, v] of Object.entries(operations[i].retries))
@@ -1254,7 +1253,7 @@ async function processRequest(
sessionContext, sessionContext,
f, f,
operations[i].provisions, operations[i].provisions,
operations[i].channels operations[i].channels,
); );
} }
@@ -1264,7 +1263,7 @@ async function processRequest(
export async function listener( export async function listener(
httpRequest: IncomingMessage, httpRequest: IncomingMessage,
httpResponse: ServerResponse httpResponse: ServerResponse,
): Promise<void> { ): Promise<void> {
stats.concurrentRequests += 1; stats.concurrentRequests += 1;
try { try {
@@ -1282,7 +1281,7 @@ async function clientError(
httpResponse: ServerResponse, httpResponse: ServerResponse,
sessionContext: SessionContext, sessionContext: SessionContext,
body: string, body: string,
msg: string msg: string,
): Promise<void> { ): Promise<void> {
let debugEnabled = false; let debugEnabled = false;
let deviceId: string = null; let deviceId: string = null;
@@ -1303,7 +1302,7 @@ async function clientError(
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS") if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress; return getRequestOrigin(httpRequest).remoteAddress;
return e; return e;
} },
); );
} }
@@ -1330,7 +1329,7 @@ function decodeString(buffer: Buffer, charset: string): string {
async function listenerAsync( async function listenerAsync(
httpRequest: IncomingMessage, httpRequest: IncomingMessage,
httpResponse: ServerResponse httpResponse: ServerResponse,
): Promise<void> { ): Promise<void> {
stats.totalRequests += 1; stats.totalRequests += 1;
@@ -1419,7 +1418,7 @@ async function listenerAsync(
httpResponse, httpResponse,
sessionContext, sessionContext,
body.toString(), body.toString(),
"Invalid session" "Invalid session",
); );
} }
} }
@@ -1427,7 +1426,7 @@ async function listenerAsync(
let charset: string; let charset: string;
if (httpRequest.headers["content-type"]) { if (httpRequest.headers["content-type"]) {
const m = httpRequest.headers["content-type"].match( const m = httpRequest.headers["content-type"].match(
/charset=['"]?([^'"\s]+)/i /charset=['"]?([^'"\s]+)/i,
); );
if (m) charset = m[1].toLowerCase(); if (m) charset = m[1].toLowerCase();
} }
@@ -1465,7 +1464,7 @@ async function listenerAsync(
httpResponse, httpResponse,
sessionContext, sessionContext,
body.toString(), body.toString(),
msg msg,
); );
} }
@@ -1498,7 +1497,7 @@ async function listenerAsync(
httpResponse, httpResponse,
sessionContext, sessionContext,
bodyStr, bodyStr,
err.message err.message,
); );
} }
@@ -1531,7 +1530,7 @@ async function listenerAsync(
httpResponse, httpResponse,
null, null,
bodyStr, bodyStr,
"Invalid session" "Invalid session",
); );
} }
@@ -1552,7 +1551,7 @@ async function listenerAsync(
const _sessionContext = session.init( const _sessionContext = session.init(
deviceId, deviceId,
rpc.cwmpVersion, rpc.cwmpVersion,
rpc.sessionTimeout rpc.sessionTimeout,
); );
_sessionContext.cacheSnapshot = cacheSnapshot; _sessionContext.cacheSnapshot = cacheSnapshot;
@@ -1584,7 +1583,7 @@ async function listenerAsync(
const parameters = await fetchDevice( const parameters = await fetchDevice(
_sessionContext.deviceId, _sessionContext.deviceId,
_sessionContext.timestamp _sessionContext.timestamp,
); );
if (parameters) { if (parameters) {
+20 -17
View File
@@ -22,7 +22,7 @@ function compareAccessLists(list1: string[], list2: string[]): boolean {
export async function fetchDevice( export async function fetchDevice(
id: string, id: string,
timestamp: number timestamp: number,
): Promise<[Path, number, Attributes?][]> { ): Promise<[Path, number, Attributes?][]> {
const res: [Path, number, Attributes?][] = [ const res: [Path, number, Attributes?][] = [
[ [
@@ -44,7 +44,7 @@ export async function fetchDevice(
obj, obj,
path: string, path: string,
pathLength: number, pathLength: number,
ts: number ts: number,
): void { ): void {
if (obj["_timestamp"]) obj["_timestamp"] = +obj["_timestamp"]; if (obj["_timestamp"]) obj["_timestamp"] = +obj["_timestamp"];
if (obj["_attributesTimestamp"]) if (obj["_attributesTimestamp"])
@@ -243,7 +243,7 @@ export async function saveDevice(
deviceId: string, deviceId: string,
deviceData: DeviceData, deviceData: DeviceData,
isNew: boolean, isNew: boolean,
sessionTimestamp: number sessionTimestamp: number,
): Promise<void> { ): Promise<void> {
const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} }; const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} };
@@ -357,7 +357,7 @@ export async function saveDevice(
if (!update["$addToSet"]["_tags"]) if (!update["$addToSet"]["_tags"])
update["$addToSet"]["_tags"] = { $each: [] }; update["$addToSet"]["_tags"] = { $each: [] };
update["$addToSet"]["_tags"]["$each"].push( update["$addToSet"]["_tags"]["$each"].push(
decodeTag(path.segments[1] as string) decodeTag(path.segments[1] as string),
); );
} else { } else {
if (!update["$pull"]["_tags"]) { if (!update["$pull"]["_tags"]) {
@@ -366,7 +366,7 @@ export async function saveDevice(
}; };
} }
update["$pull"]["_tags"]["$in"].push( update["$pull"]["_tags"]["$in"].push(
decodeTag(path.segments[1] as string) decodeTag(path.segments[1] as string),
); );
} }
} }
@@ -379,7 +379,10 @@ export async function saveDevice(
if (pathStr.endsWith(INVALID_PATH_SUFFIX)) { if (pathStr.endsWith(INVALID_PATH_SUFFIX)) {
const splits = pathStr.split("."); const splits = pathStr.split(".");
splits[splits.length - 1] = decodeTag( splits[splits.length - 1] = decodeTag(
splits[splits.length - 1].slice(0, 0 - INVALID_PATH_SUFFIX.length) splits[splits.length - 1].slice(
0,
0 - INVALID_PATH_SUFFIX.length,
),
); );
pathStr = splits.join("."); pathStr = splits.join(".");
} }
@@ -401,7 +404,7 @@ export async function saveDevice(
Number.isInteger(value2 as number) Number.isInteger(value2 as number)
) { ) {
update["$set"][path.toString() + "._value"] = new Date( update["$set"][path.toString() + "._value"] = new Date(
value2 as number value2 as number,
); );
} else { } else {
update["$set"][path.toString() + "._value"] = value2; update["$set"][path.toString() + "._value"] = value2;
@@ -413,7 +416,7 @@ export async function saveDevice(
if (valueTimestamp2 !== valueTimestamp1) { if (valueTimestamp2 !== valueTimestamp1) {
update["$set"][path.toString() + "._timestamp"] = new Date( update["$set"][path.toString() + "._timestamp"] = new Date(
valueTimestamp2 valueTimestamp2,
); );
} }
@@ -533,7 +536,7 @@ export async function saveDevice(
update, update,
{ {
upsert: isNew, upsert: isNew,
} },
); );
if (!result.matchedCount && !result.upsertedCount) if (!result.matchedCount && !result.upsertedCount)
@@ -546,7 +549,7 @@ export async function saveDevice(
} }
export async function getFaults( export async function getFaults(
deviceId: string deviceId: string,
): Promise<{ [channel: string]: SessionFault }> { ): Promise<{ [channel: string]: SessionFault }> {
const res = await collections.faults const res = await collections.faults
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } }) .find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
@@ -573,7 +576,7 @@ export async function getFaults(
export async function saveFault( export async function saveFault(
deviceId: string, deviceId: string,
channel: string, channel: string,
fault: SessionFault fault: SessionFault,
): Promise<void> { ): Promise<void> {
const id = `${deviceId}:${channel}`; const id = `${deviceId}:${channel}`;
const f: MongoTypes.Fault = { const f: MongoTypes.Fault = {
@@ -593,14 +596,14 @@ export async function saveFault(
export async function deleteFault( export async function deleteFault(
deviceId: string, deviceId: string,
channel: string channel: string,
): Promise<void> { ): Promise<void> {
await collections.faults.deleteOne({ _id: `${deviceId}:${channel}` }); await collections.faults.deleteOne({ _id: `${deviceId}:${channel}` });
} }
export async function getDueTasks( export async function getDueTasks(
deviceId: string, deviceId: string,
timestamp: number timestamp: number,
): Promise<[Task[], number]> { ): Promise<[Task[], number]> {
const cur = collections.tasks const cur = collections.tasks
.find({ device: deviceId }) .find({ device: deviceId })
@@ -664,7 +667,7 @@ export async function getDueTasks(
export async function clearTasks( export async function clearTasks(
deviceId: string, deviceId: string,
taskIds: string[] taskIds: string[],
): Promise<void> { ): Promise<void> {
await collections.tasks.deleteMany({ await collections.tasks.deleteMany({
_id: { $in: taskIds.map((id) => new ObjectId(id)) }, _id: { $in: taskIds.map((id) => new ObjectId(id)) },
@@ -672,7 +675,7 @@ export async function clearTasks(
} }
export async function getOperations( export async function getOperations(
deviceId: string deviceId: string,
): Promise<{ [commandKey: string]: Operation }> { ): Promise<{ [commandKey: string]: Operation }> {
const res = await collections.operations const res = await collections.operations
.find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } }) .find({ _id: { $regex: `^${escapeRegExp(deviceId)}\\:` } })
@@ -704,7 +707,7 @@ export async function getOperations(
export async function saveOperation( export async function saveOperation(
deviceId: string, deviceId: string,
commandKey: string, commandKey: string,
operation: Operation operation: Operation,
): Promise<void> { ): Promise<void> {
const id = `${deviceId}:${commandKey}`; const id = `${deviceId}:${commandKey}`;
const o: MongoTypes.Operation = { const o: MongoTypes.Operation = {
@@ -723,7 +726,7 @@ export async function saveOperation(
export async function deleteOperation( export async function deleteOperation(
deviceId: string, deviceId: string,
commandKey: string commandKey: string,
): Promise<void> { ): Promise<void> {
await collections.operations.deleteOne({ _id: `${deviceId}:${commandKey}` }); await collections.operations.deleteOne({ _id: `${deviceId}:${commandKey}` });
} }
+7 -7
View File
@@ -28,7 +28,7 @@ interface Snapshot {
function flattenObject<T extends Record<keyof T, unknown>>( function flattenObject<T extends Record<keyof T, unknown>>(
src: T, src: T,
prefix = "", prefix = "",
dst = {} as T dst = {} as T,
): T { ): T {
for (const k of Object.keys(src)) { for (const k of Object.keys(src)) {
const v = src[k]; const v = src[k];
@@ -99,7 +99,7 @@ async function fetchPresets(): Promise<[string, Preset[]]> {
precondition = parse(preset["precondition"]); precondition = parse(preset["precondition"]);
} catch (error) { } catch (error) {
precondition = convertOldPrecondition( precondition = convertOldPrecondition(
JSON.parse(preset["precondition"]) JSON.parse(preset["precondition"]),
); );
} }
@@ -166,7 +166,7 @@ async function fetchPresets(): Promise<[string, Preset[]]> {
default: { default: {
const exhaustiveCheck: never = c; const exhaustiveCheck: never = c;
throw new Error( throw new Error(
`Unknown configuration type ${exhaustiveCheck["type"]}` `Unknown configuration type ${exhaustiveCheck["type"]}`,
); );
} }
} }
@@ -199,7 +199,7 @@ async function fetchProvisions(): Promise<[string, Provisions]> {
.digest("hex"); .digest("hex");
provisions[r._id].script = new vm.Script( provisions[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`, `"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1 } { filename: r._id, lineOffset: -1 },
); );
} }
@@ -220,7 +220,7 @@ async function fetchVirtualParameters(): Promise<[string, VirtualParameters]> {
.digest("hex"); .digest("hex");
virtualParameters[r._id].script = new vm.Script( virtualParameters[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`, `"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1 } { filename: r._id, lineOffset: -1 },
); );
} }
@@ -307,7 +307,7 @@ export function getConfig(
key: string, key: string,
context: Record<string, unknown>, context: Record<string, unknown>,
now: number, now: number,
cb?: (e: Expression) => Expression cb?: (e: Expression) => Expression,
): string | number | boolean | null { ): string | number | boolean | null {
const snapshot = localCache.get(snapshotKey); const snapshot = localCache.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
@@ -350,7 +350,7 @@ export function getConfig(
export function getConfigExpression( export function getConfigExpression(
snapshotKey: string, snapshotKey: string,
key: string key: string,
): Expression { ): Expression {
const snapshot = localCache.get(snapshotKey); const snapshot = localCache.get(snapshotKey);
return snapshot.config[key]; return snapshot.config[key];
+25 -22
View File
@@ -131,7 +131,7 @@ function roundOid(oid: string, roundUp: boolean): string {
function groupBy<T, K>( function groupBy<T, K>(
input: T[], input: T[],
callback: (item: T) => K callback: (item: T) => K,
): Iterable<[K, T[]]> { ): Iterable<[K, T[]]> {
const groups = new Map<K, T[]>(); const groups = new Map<K, T[]>();
for (const item of input) { for (const item of input) {
@@ -155,7 +155,7 @@ abstract class MongoClause {
class MongoClauseArray extends MongoClause { class MongoClauseArray extends MongoClause {
constructor( constructor(
public readonly parameter: string, public readonly parameter: string,
public readonly value: string public readonly value: string,
) { ) {
super(); super();
} }
@@ -171,7 +171,7 @@ class MongoClauseCompare<T> extends MongoClause {
public readonly parameter: string, public readonly parameter: string,
public readonly op: "$eq" | "$gt" | "$lt" | "$gte" | "$lte", public readonly op: "$eq" | "$gt" | "$lt" | "$gte" | "$lte",
public readonly value: T, public readonly value: T,
public readonly type: "" | "bool" | "number" | "string" | "date" | "oid" public readonly type: "" | "bool" | "number" | "string" | "date" | "oid",
) { ) {
super(); super();
} }
@@ -189,7 +189,10 @@ class MongoClauseCompare<T> extends MongoClause {
} }
class MongoClauseType extends MongoClause { class MongoClauseType extends MongoClause {
constructor(public readonly parameter: string, public readonly type: string) { constructor(
public readonly parameter: string,
public readonly type: string,
) {
super(); super();
} }
@@ -205,7 +208,7 @@ class MongoClauseLike extends MongoClause {
public readonly parameter: string, public readonly parameter: string,
pat: string, pat: string,
esc: string, esc: string,
public readonly caseSensitive: boolean public readonly caseSensitive: boolean,
) { ) {
super(); super();
this.pattern = parseLikePattern(pat, esc); this.pattern = parseLikePattern(pat, esc);
@@ -357,8 +360,8 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
param, param,
"$gte", "$gte",
"000000000000000000000000", "000000000000000000000000",
"oid" "oid",
) ),
); );
} }
} }
@@ -417,30 +420,30 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
for (const [parameter, clauses2] of groupBy(clauses, (c) => c.parameter)) { for (const [parameter, clauses2] of groupBy(clauses, (c) => c.parameter)) {
const comparisons = clauses2.filter( const comparisons = clauses2.filter(
(c) => c instanceof MongoClauseCompare && c.value !== null (c) => c instanceof MongoClauseCompare && c.value !== null,
) as MongoClauseCompare<unknown>[]; ) as MongoClauseCompare<unknown>[];
for (const [type, clauses3] of groupBy(comparisons, (c) => c.type)) { for (const [type, clauses3] of groupBy(comparisons, (c) => c.type)) {
const isType = this.getVar(new MongoClauseType(parameter, type)); const isType = this.getVar(new MongoClauseType(parameter, type));
const values = new Set(clauses3.map((c) => c.value)); const values = new Set(clauses3.map((c) => c.value));
const valuesSorted = Array.from(values).sort((a, b) => const valuesSorted = Array.from(values).sort((a, b) =>
a > b ? 1 : -1 a > b ? 1 : -1,
); );
for (const [i, v] of valuesSorted.entries()) { for (const [i, v] of valuesSorted.entries()) {
const eq = this.getVar( const eq = this.getVar(
new MongoClauseCompare(parameter, "$eq", v, type) new MongoClauseCompare(parameter, "$eq", v, type),
); );
const gt = this.getVar( const gt = this.getVar(
new MongoClauseCompare(parameter, "$gt", v, type) new MongoClauseCompare(parameter, "$gt", v, type),
); );
const lt = this.getVar( const lt = this.getVar(
new MongoClauseCompare(parameter, "$lt", v, type) new MongoClauseCompare(parameter, "$lt", v, type),
); );
const gte = this.getVar( const gte = this.getVar(
new MongoClauseCompare(parameter, "$gte", v, type) new MongoClauseCompare(parameter, "$gte", v, type),
); );
const lte = this.getVar( const lte = this.getVar(
new MongoClauseCompare(parameter, "$lte", v, type) new MongoClauseCompare(parameter, "$lte", v, type),
); );
if (type === "bool") { if (type === "bool") {
@@ -472,16 +475,16 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
parameter, parameter,
"$gt", "$gt",
valuesSorted[i - 1], valuesSorted[i - 1],
type type,
) ),
); );
const lte2 = this.getVar( const lte2 = this.getVar(
new MongoClauseCompare( new MongoClauseCompare(
parameter, parameter,
"$lte", "$lte",
valuesSorted[i - 1], valuesSorted[i - 1],
type type,
) ),
); );
dcSet.push([(gt2 << 1) ^ 0, (gte << 1) ^ 1]); dcSet.push([(gt2 << 1) ^ 0, (gte << 1) ^ 1]);
dcSet.push([(gt2 << 1) ^ 0, (lte2 << 1) ^ 0, (lt << 1) ^ 1]); dcSet.push([(gt2 << 1) ^ 0, (lte2 << 1) ^ 0, (lt << 1) ^ 1]);
@@ -493,7 +496,7 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
} }
const likes = clauses2.filter( const likes = clauses2.filter(
(c) => c instanceof MongoClauseLike (c) => c instanceof MongoClauseLike,
) as MongoClauseLike[]; ) as MongoClauseLike[];
if (likes.length) { if (likes.length) {
const isType = this.getVar(new MongoClauseType(parameter, "string")); const isType = this.getVar(new MongoClauseType(parameter, "string"));
@@ -535,11 +538,11 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
} }
const isNull = this.getVar( const isNull = this.getVar(
new MongoClauseCompare(parameter, "$eq", null, "") new MongoClauseCompare(parameter, "$eq", null, ""),
); );
const types = getTypes(parameter, this.collection).map((t) => const types = getTypes(parameter, this.collection).map((t) =>
this.getVar(new MongoClauseType(parameter, t)) this.getVar(new MongoClauseType(parameter, t)),
); );
dcSet.push([(isNull << 1) ^ 0, ...types.map((t) => (t << 1) ^ 0)]); dcSet.push([(isNull << 1) ^ 0, ...types.map((t) => (t << 1) ^ 0)]);
@@ -621,7 +624,7 @@ class MongoSynthContext extends SynthContextBase<Clause, MongoClause> {
export function toMongoQuery( export function toMongoQuery(
exp: Expression, exp: Expression,
resource: string resource: string,
): Filter<unknown> | false { ): Filter<unknown> | false {
exp = normalize(exp); exp = normalize(exp);
const clause = Clause.fromExpression(normalize(exp)); const clause = Clause.fromExpression(normalize(exp));
+6 -6
View File
@@ -41,7 +41,7 @@ function getConnectionTimestamp(connection: Socket): Date {
export function incomingHttpRequest( export function incomingHttpRequest(
httpRequest: IncomingMessage, httpRequest: IncomingMessage,
deviceId: string, deviceId: string,
body: string body: string,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
@@ -70,7 +70,7 @@ export function incomingHttpRequest(
export function outgoingHttpResponse( export function outgoingHttpResponse(
httpResponse: ServerResponse, httpResponse: ServerResponse,
deviceId: string, deviceId: string,
body: string body: string,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
@@ -99,7 +99,7 @@ export function outgoingHttpRequest(
deviceId: string, deviceId: string,
method: "GET" | "PUT" | "POST" | "DELETE", method: "GET" | "PUT" | "POST" | "DELETE",
url: URL, url: URL,
body: string body: string,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
@@ -129,7 +129,7 @@ export function outgoingHttpRequestError(
deviceId: string, deviceId: string,
method: "GET" | "PUT" | "POST" | "DELETE", method: "GET" | "PUT" | "POST" | "DELETE",
url: URL, url: URL,
err: Error err: Error,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
@@ -156,7 +156,7 @@ export function outgoingHttpRequestError(
export function incomingHttpResponse( export function incomingHttpResponse(
httpResponse: IncomingMessage, httpResponse: IncomingMessage,
deviceId: string, deviceId: string,
body: string body: string,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
@@ -183,7 +183,7 @@ export function outgoingUdpMessage(
remoteAddress: string, remoteAddress: string,
deviceId: string, deviceId: string,
remotePort: number, remotePort: number,
body: string body: string,
): void { ): void {
if (!DEBUG_FILE) return; if (!DEBUG_FILE) return;
const now = new Date(); const now = new Date();
+8 -8
View File
@@ -28,7 +28,7 @@ const MAX_DEPTH = +config.get("MAX_DEPTH");
export function refresh( export function refresh(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if ( if (
(provision.length !== 2 || typeof provision[1] !== "string") && (provision.length !== 2 || typeof provision[1] !== "string") &&
@@ -84,7 +84,7 @@ export function refresh(
export function value( export function value(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if ( if (
provision.length < 3 || provision.length < 3 ||
@@ -127,7 +127,7 @@ export function value(
export function tag( export function tag(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if ( if (
provision.length !== 3 || provision.length !== 3 ||
@@ -151,7 +151,7 @@ export function tag(
export function reboot( export function reboot(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if (provision.length !== 1) throw new Error("Invalid arguments"); if (provision.length !== 1) throw new Error("Invalid arguments");
@@ -170,7 +170,7 @@ export function reboot(
export function reset( export function reset(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if (provision.length !== 1) throw new Error("Invalid arguments"); if (provision.length !== 1) throw new Error("Invalid arguments");
@@ -189,7 +189,7 @@ export function reset(
export function download( export function download(
sessionContext: SessionContext, sessionContext: SessionContext,
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[] declarations: Declaration[],
): boolean { ): boolean {
if ( if (
(provision.length !== 3 || (provision.length !== 3 ||
@@ -234,7 +234,7 @@ export function instances(
provision: (string | number | boolean)[], provision: (string | number | boolean)[],
declarations: Declaration[], declarations: Declaration[],
startRevision: number, startRevision: number,
endRevision: number endRevision: number,
): boolean { ): boolean {
if (provision.length !== 3 || typeof provision[1] !== "string") if (provision.length !== 3 || typeof provision[1] !== "string")
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
@@ -260,7 +260,7 @@ export function instances(
const unpacked = device.unpack( const unpacked = device.unpack(
sessionContext.deviceData, sessionContext.deviceData,
path, path,
startRevision + 1 startRevision + 1,
); );
count = Math.max(0, unpacked.length + count); count = Math.max(0, unpacked.length + count);
} }
+11 -11
View File
@@ -43,7 +43,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]) {
@@ -87,7 +87,7 @@ export function sanitizeParameterValue(
export function getAliasDeclarations( export function getAliasDeclarations(
path: Path, path: Path,
timestamp: number, timestamp: number,
attrGet = null attrGet = null,
): Declaration[] { ): Declaration[] {
const stripped = path.stripAlias(); const stripped = path.stripAlias();
let decs: Declaration[] = [ let decs: Declaration[] = [
@@ -109,7 +109,7 @@ export function getAliasDeclarations(
decs = decs.concat( decs = decs.concat(
getAliasDeclarations(parent.concat(p), timestamp, { getAliasDeclarations(parent.concat(p), timestamp, {
value: timestamp, value: timestamp,
}) }),
); );
} }
} }
@@ -122,7 +122,7 @@ export function getAliasDeclarations(
export function unpack( export function unpack(
deviceData: DeviceData, deviceData: DeviceData,
path: Path, path: Path,
revision?: number revision?: number,
): Path[] { ): Path[] {
let allMatches = [] as Path[]; let allMatches = [] as Path[];
if (!path.alias) { if (!path.alias) {
@@ -194,7 +194,7 @@ export function clear(
path: Path, path: Path,
timestamp: number, timestamp: number,
attributes: AttributeTimestamps, attributes: AttributeTimestamps,
changeFlags = 0 changeFlags = 0,
): void { ): void {
const changeTrackers = {}; const changeTrackers = {};
@@ -212,7 +212,7 @@ export function clear(
path, path,
true, true,
true, true,
descendantsTimestamp ? 99 : path.length descendantsTimestamp ? 99 : path.length,
)) { )) {
const tracker = deviceData.trackers.get(p); const tracker = deviceData.trackers.get(p);
for (const k in tracker) changeTrackers[k] |= tracker[k]; for (const k in tracker) changeTrackers[k] |= tracker[k];
@@ -274,7 +274,7 @@ export function set(
path: Path, path: Path,
timestamp: number, timestamp: number,
attributes: Attributes, attributes: Attributes,
toClear?: Clear[] toClear?: Clear[],
): Clear[] { ): Clear[] {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
@@ -315,7 +315,7 @@ export function set(
else if ( else if (
!compareEquality( !compareEquality(
attributes[attrName][1], attributes[attrName][1],
currentAttributes[attrName][1] currentAttributes[attrName][1],
) )
) )
changeFlags |= CHANGE_FLAGS[attrName]; changeFlags |= CHANGE_FLAGS[attrName];
@@ -334,7 +334,7 @@ export function set(
path.slice(0, path.length - 1), path.slice(0, path.length - 1),
timestamp, timestamp,
{ object: [timestamp, 1] }, { object: [timestamp, 1] },
toClear toClear,
); );
} }
} }
@@ -377,7 +377,7 @@ export function track(
deviceData: DeviceData, deviceData: DeviceData,
path: Path, path: Path,
marker: string, marker: string,
attributes?: string[] attributes?: string[],
): void { ): void {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
let f = 1; let f = 1;
@@ -396,7 +396,7 @@ export function track(
export function clearTrackers( export function clearTrackers(
deviceData: DeviceData, deviceData: DeviceData,
tracker: string | string[] tracker: string | string[],
): void { ): void {
if (Array.isArray(tracker)) { if (Array.isArray(tracker)) {
for (const v of deviceData.trackers.values()) for (const v of deviceData.trackers.values())
+1 -1
View File
@@ -123,6 +123,6 @@ export async function killAll(): Promise<void> {
Object.entries(processes).map(([k, p]) => { Object.entries(processes).map(([k, p]) => {
delete processes[k]; delete processes[k];
return kill(p); return kill(p);
}) }),
); );
} }
+5 -5
View File
@@ -30,7 +30,7 @@ const getFile = memoize(
async ( async (
etag: string, etag: string,
size: number, size: number,
filename: string filename: string,
): Promise<Iterable<Buffer>> => { ): Promise<Iterable<Buffer>> => {
const chunks: Buffer[] = []; const chunks: Buffer[] = [];
// Using for-await over the download stream can throw ERR_STREAM_PREMATURE_CLOSE // Using for-await over the download stream can throw ERR_STREAM_PREMATURE_CLOSE
@@ -41,7 +41,7 @@ const getFile = memoize(
new PassThrough(), new PassThrough(),
(err) => { (err) => {
if (err) throw err; if (err) throw err;
} },
); );
for await (const chunk of downloadStream) chunks.push(chunk); for await (const chunk of downloadStream) chunks.push(chunk);
// Node 12-14 don't throw error when stream is closed prematurely. // Node 12-14 don't throw error when stream is closed prematurely.
@@ -49,13 +49,13 @@ const getFile = memoize(
if (size !== chunks.reduce((a, b) => a + b.length, 0)) if (size !== chunks.reduce((a, b) => a + b.length, 0))
throw new Error("File size mismatch"); throw new Error("File size mismatch");
return chunks; return chunks;
} },
); );
async function* partialContent( async function* partialContent(
chunks: Iterable<Buffer>, chunks: Iterable<Buffer>,
start: number, start: number,
end: number end: number,
): AsyncIterable<Buffer> { ): AsyncIterable<Buffer> {
let bytesToSkip = start; let bytesToSkip = start;
let bytesToRead = end - start; let bytesToRead = end - start;
@@ -100,7 +100,7 @@ function matchEtag(etag: string, header: string): boolean {
export async function listener( export async function listener(
request: IncomingMessage, request: IncomingMessage,
response: ServerResponse response: ServerResponse,
): Promise<void> { ): Promise<void> {
if (request.method !== "GET" && request.method !== "HEAD") { if (request.method !== "GET" && request.method !== "HEAD") {
response.writeHead(405, { Allow: "GET, HEAD" }); response.writeHead(405, { Allow: "GET, HEAD" });
+1 -1
View File
@@ -31,7 +31,7 @@ const UNDISCOVERED_DEPTH = 7;
// point has been discovered. // point has been discovered.
export function estimateGpnCount( export function estimateGpnCount(
gpnPatterns: [Path, number][], gpnPatterns: [Path, number][],
depth = 0 depth = 0,
): number { ): number {
const children: { [segment: string]: [Path, number][] } = {}; const children: { [segment: string]: [Path, number][] } = {};
const wildcardChildren: [Path, number][] = []; const wildcardChildren: [Path, number][] = [];
+1 -1
View File
@@ -31,7 +31,7 @@ export class LocalCache<T> {
constructor( constructor(
private cacheKey: string, private cacheKey: string,
private callback: () => Promise<[string, T]> private callback: () => Promise<[string, T]>,
) {} ) {}
async getRevision(): Promise<string> { async getRevision(): Promise<string> {
+3 -3
View File
@@ -6,7 +6,7 @@ export async function acquireLock(
lockName: string, lockName: string,
ttl: number, ttl: number,
timeout = 0, timeout = 0,
token = Math.random().toString(36).slice(2) token = Math.random().toString(36).slice(2),
): Promise<string> { ): Promise<string> {
try { try {
const now = Date.now(); const now = Date.now();
@@ -18,7 +18,7 @@ export async function acquireLock(
}, },
$currentDate: { timestamp: true }, $currentDate: { timestamp: true },
}, },
{ upsert: true, returnDocument: "after" } { upsert: true, returnDocument: "after" },
); );
if (Math.abs(r.value.timestamp.getTime() - now) > CLOCK_SKEW_TOLERANCE) if (Math.abs(r.value.timestamp.getTime() - now) > CLOCK_SKEW_TOLERANCE)
throw new Error("Database clock skew too great"); throw new Error("Database clock skew too great");
@@ -35,7 +35,7 @@ export async function acquireLock(
export async function releaseLock( export async function releaseLock(
lockName: string, lockName: string,
token: string token: string,
): Promise<void> { ): Promise<void> {
const res = await collections.locks.deleteOne({ const res = await collections.locks.deleteOne({
_id: lockName, _id: lockName,
+5 -5
View File
@@ -156,13 +156,13 @@ export function close(): void {
} }
export function flatten( export function flatten(
details: Record<string, unknown> details: Record<string, unknown>,
): Record<string, unknown> { ): Record<string, unknown> {
if (details.sessionContext) { if (details.sessionContext) {
const sessionContext = details.sessionContext as SessionContext; const sessionContext = details.sessionContext as SessionContext;
details.deviceId = sessionContext.deviceId; details.deviceId = sessionContext.deviceId;
details.remoteAddress = getRequestOrigin( details.remoteAddress = getRequestOrigin(
sessionContext.httpRequest sessionContext.httpRequest,
).remoteAddress; ).remoteAddress;
delete details.sessionContext; delete details.sessionContext;
} }
@@ -220,7 +220,7 @@ export function flatten(
// For genieacs-ui // For genieacs-ui
if (details.context) { if (details.context) {
details.remoteAddress = getRequestOrigin( details.remoteAddress = getRequestOrigin(
details.context["req"] details.context["req"],
).remoteAddress; ).remoteAddress;
if (details.context["state"].user) if (details.context["state"].user)
details.user = details.context["state"].user.username; details.user = details.context["state"].user.username;
@@ -235,7 +235,7 @@ export function flatten(
function formatJson( function formatJson(
details: Record<string, unknown>, details: Record<string, unknown>,
systemd: boolean systemd: boolean,
): string { ): string {
if (systemd) { if (systemd) {
let severity = ""; let severity = "";
@@ -251,7 +251,7 @@ function formatJson(
function formatSimple( function formatSimple(
details: Record<string, unknown>, details: Record<string, unknown>,
systemd: boolean systemd: boolean,
): string { ): string {
const skip = { const skip = {
user: true, user: true,
+18 -18
View File
@@ -65,14 +65,14 @@ async function getBody(request: IncomingMessage): Promise<Buffer> {
export async function listener( export async function listener(
request: IncomingMessage, request: IncomingMessage,
response: ServerResponse response: ServerResponse,
): Promise<void> { ): Promise<void> {
response.setHeader("GenieACS-Version", VERSION); response.setHeader("GenieACS-Version", VERSION);
const origin = getRequestOrigin(request); const origin = getRequestOrigin(request);
const url = new URL( const url = new URL(
request.url, request.url,
(origin.encrypted ? "https://" : "http://") + origin.host (origin.encrypted ? "https://" : "http://") + origin.host,
); );
const body = await getBody(request).catch(() => null); const body = await getBody(request).catch(() => null);
@@ -83,7 +83,7 @@ export async function listener(
Object.assign({}, Object.fromEntries(url.searchParams), { Object.assign({}, Object.fromEntries(url.searchParams), {
remoteAddress: origin.remoteAddress, remoteAddress: origin.remoteAddress,
message: `${request.method} ${url.pathname}`, message: `${request.method} ${url.pathname}`,
}) }),
); );
return handler(request, response, url, body); return handler(request, response, url, body);
} }
@@ -92,7 +92,7 @@ async function handler(
request: IncomingMessage, request: IncomingMessage,
response: ServerResponse, response: ServerResponse,
url: URL, url: URL,
body: Buffer body: Buffer,
): Promise<void> { ): Promise<void> {
if (PRESETS_REGEX.test(url.pathname)) { if (PRESETS_REGEX.test(url.pathname)) {
const presetName = decodeURIComponent(PRESETS_REGEX.exec(url.pathname)[1]); const presetName = decodeURIComponent(PRESETS_REGEX.exec(url.pathname)[1]);
@@ -150,7 +150,7 @@ async function handler(
} }
} else if (PROVISIONS_REGEX.test(url.pathname)) { } else if (PROVISIONS_REGEX.test(url.pathname)) {
const provisionName = decodeURIComponent( const provisionName = decodeURIComponent(
PROVISIONS_REGEX.exec(url.pathname)[1] PROVISIONS_REGEX.exec(url.pathname)[1],
); );
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
@@ -183,7 +183,7 @@ async function handler(
} }
} else if (VIRTUAL_PARAMETERS_REGEX.test(url.pathname)) { } else if (VIRTUAL_PARAMETERS_REGEX.test(url.pathname)) {
const virtualParameterName = decodeURIComponent( const virtualParameterName = decodeURIComponent(
VIRTUAL_PARAMETERS_REGEX.exec(url.pathname)[1] VIRTUAL_PARAMETERS_REGEX.exec(url.pathname)[1],
); );
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
@@ -202,7 +202,7 @@ async function handler(
await collections.virtualParameters.replaceOne( await collections.virtualParameters.replaceOne(
{ _id: virtualParameterName }, { _id: virtualParameterName },
object, object,
{ upsert: true } { upsert: true },
); );
await cache.del("cwmp-local-cache-hash"); await cache.del("cwmp-local-cache-hash");
response.writeHead(200); response.writeHead(200);
@@ -225,7 +225,7 @@ async function handler(
if (request.method === "POST") { if (request.method === "POST") {
const updateRes = await collections.devices.updateOne( const updateRes = await collections.devices.updateOne(
{ _id: deviceId }, { _id: deviceId },
{ $addToSet: { _tags: tag } } { $addToSet: { _tags: tag } },
); );
if (!updateRes.matchedCount) { if (!updateRes.matchedCount) {
@@ -239,7 +239,7 @@ async function handler(
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
const updateRes = await collections.devices.updateOne( const updateRes = await collections.devices.updateOne(
{ _id: deviceId }, { _id: deviceId },
{ $pull: { _tags: tag } } { $pull: { _tags: tag } },
); );
if (!updateRes.matchedCount) { if (!updateRes.matchedCount) {
@@ -277,7 +277,7 @@ async function handler(
} else if (DEVICE_TASKS_REGEX.test(url.pathname)) { } else if (DEVICE_TASKS_REGEX.test(url.pathname)) {
if (request.method === "POST") { if (request.method === "POST") {
const deviceId = decodeURIComponent( const deviceId = decodeURIComponent(
DEVICE_TASKS_REGEX.exec(url.pathname)[1] DEVICE_TASKS_REGEX.exec(url.pathname)[1],
); );
const conReq = url.searchParams.has("connection_request"); const conReq = url.searchParams.has("connection_request");
@@ -314,7 +314,7 @@ async function handler(
} else { } else {
const status = await apiFunctions.connectionRequest( const status = await apiFunctions.connectionRequest(
deviceId, deviceId,
flattenDevice(dev) flattenDevice(dev),
); );
if (status) { if (status) {
response.writeHead(504, status); response.writeHead(504, status);
@@ -394,7 +394,7 @@ async function handler(
} }
} }
return exp; return exp;
} },
) as number; ) as number;
} }
@@ -403,14 +403,14 @@ async function handler(
const sessionStarted = await apiFunctions.awaitSessionStart( const sessionStarted = await apiFunctions.awaitSessionStart(
deviceId, deviceId,
lastInform, lastInform,
onlineThreshold onlineThreshold,
); );
if (!sessionStarted) { if (!sessionStarted) {
status = "Task queued but not processed"; status = "Task queued but not processed";
} else { } else {
const sessionEnded = await apiFunctions.awaitSessionEnd( const sessionEnded = await apiFunctions.awaitSessionEnd(
deviceId, deviceId,
120000 120000,
); );
if (!sessionEnded) { if (!sessionEnded) {
status = "Task queued but not processed"; status = "Task queued but not processed";
@@ -445,7 +445,7 @@ async function handler(
if (request.method === "DELETE") { if (request.method === "DELETE") {
const task = await collections.tasks.findOne( const task = await collections.tasks.findOne(
{ _id: new ObjectId(taskId) }, { _id: new ObjectId(taskId) },
{ projection: { device: 1 } } { projection: { device: 1 } },
); );
if (!task) { if (!task) {
@@ -481,7 +481,7 @@ async function handler(
if (request.method === "POST") { if (request.method === "POST") {
const task = await collections.tasks.findOne( const task = await collections.tasks.findOne(
{ _id: new ObjectId(taskId) }, { _id: new ObjectId(taskId) },
{ projection: { device: 1 } } { projection: { device: 1 } },
); );
const deviceId = task.device; const deviceId = task.device;
@@ -530,7 +530,7 @@ async function handler(
filename, filename,
{ {
metadata: metadata, metadata: metadata,
} },
); );
uploadStream.on("error", reject); uploadStream.on("error", reject);
@@ -589,7 +589,7 @@ async function handler(
} }
const deviceId = decodeURIComponent( const deviceId = decodeURIComponent(
DELETE_DEVICE_REGEX.exec(url.pathname)[1] DELETE_DEVICE_REGEX.exec(url.pathname)[1],
); );
try { try {
+1 -1
View File
@@ -88,7 +88,7 @@ export function parsePing(osPlatform: string, stdout: string): PingResult {
export function ping( export function ping(
host: string, host: string,
callback: (err: Error, res?: PingResult, stdout?: string) => void callback: (err: Error, res?: PingResult, stdout?: string) => void,
): void { ): void {
// Validate input to prevent possible remote code execution // Validate input to prevent possible remote code execution
// Credit to Alex Hordijk for reporting this vulnerability // Credit to Alex Hordijk for reporting this vulnerability
+2 -2
View File
@@ -118,7 +118,7 @@ function permute(param, val): any[] {
} }
export function expand( export function expand(
query: Record<string, unknown> query: Record<string, unknown>,
): 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)) {
@@ -147,7 +147,7 @@ export function expand(
export function sanitizeQueryTypes( export function sanitizeQueryTypes(
query: Record<string, unknown>, query: Record<string, unknown>,
types: Record<string, (v: unknown) => unknown> types: Record<string, (v: unknown) => unknown>,
): Record<string, unknown> { ): Record<string, unknown> {
for (const [k, v] of Object.entries(query)) { for (const [k, v] of Object.entries(query)) {
if (k[0] === "$") { if (k[0] === "$") {
+14 -14
View File
@@ -62,7 +62,7 @@ function runExtension(sessionContext, key, extCall): Promise<Fault> {
resolve(fault); resolve(fault);
}) })
.catch(reject); .catch(reject);
})) })),
); );
} }
@@ -78,7 +78,7 @@ class SandboxDate {
number?, number?,
number?, number?,
number?, number?,
number? number?,
] ]
) { ) {
if (argumentList.length) return new Date(...argumentList); if (argumentList.length) return new Date(...argumentList);
@@ -143,7 +143,7 @@ class ParameterWrapper {
unpacked = device.unpack( unpacked = device.unpack(
state.sessionContext.deviceData, state.sessionContext.deviceData,
path, path,
state.revision state.revision,
); );
} }
@@ -151,7 +151,7 @@ class ParameterWrapper {
const attr = state.sessionContext.deviceData.attributes.get( const attr = state.sessionContext.deviceData.attributes.get(
unpacked[0], unpacked[0],
state.revision state.revision,
)[attrName]; )[attrName];
if (!attr) return UNDEFINED; if (!attr) return UNDEFINED;
@@ -170,7 +170,7 @@ class ParameterWrapper {
unpacked = device.unpack( unpacked = device.unpack(
state.sessionContext.deviceData, state.sessionContext.deviceData,
path, path,
state.revision state.revision,
); );
} }
@@ -189,7 +189,7 @@ class ParameterWrapper {
unpacked = device.unpack( unpacked = device.unpack(
state.sessionContext.deviceData, state.sessionContext.deviceData,
path, path,
state.revision state.revision,
); );
} }
@@ -207,7 +207,7 @@ class ParameterWrapper {
unpacked = device.unpack( unpacked = device.unpack(
state.sessionContext.deviceData, state.sessionContext.deviceData,
path, path,
state.revision state.revision,
); );
} }
@@ -220,7 +220,7 @@ class ParameterWrapper {
function declare( function declare(
path: string, path: string,
timestamps: { [attr: string]: number }, timestamps: { [attr: string]: number },
values: { [attr: string]: any } values: { [attr: string]: any },
): ParameterWrapper { ): ParameterWrapper {
state.uncommitted = true; state.uncommitted = true;
if (!timestamps) timestamps = {}; if (!timestamps) timestamps = {};
@@ -286,7 +286,7 @@ function commit(): void {
throw COMMIT; throw COMMIT;
} else if (state.revision > state.maxRevision + 1) { } else if (state.revision > state.maxRevision + 1) {
throw new Error( throw new Error(
"Declare function should not be called from within a try/catch block" "Declare function should not be called from within a try/catch block",
); );
} }
} }
@@ -351,12 +351,12 @@ function errorToFault(err: Error): Fault {
fault.detail["stack"] = err.stack; fault.detail["stack"] = err.stack;
// Trim the stack trace at the self-executing anonymous wrapper function // Trim the stack trace at the self-executing anonymous wrapper function
const stackTrimIndex = fault.detail["stack"].match( const stackTrimIndex = fault.detail["stack"].match(
/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/ /\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/,
); );
if (stackTrimIndex) { if (stackTrimIndex) {
fault.detail["stack"] = fault.detail["stack"].slice( fault.detail["stack"] = fault.detail["stack"].slice(
0, 0,
stackTrimIndex.index stackTrimIndex.index,
); );
} }
} }
@@ -370,7 +370,7 @@ export async function run(
sessionContext: SessionContext, sessionContext: SessionContext,
startRevision: number, startRevision: number,
maxRevision: number, maxRevision: number,
extCounter = 0 extCounter = 0,
): Promise<ScriptResult> { ): Promise<ScriptResult> {
state = { state = {
sessionContext: sessionContext, sessionContext: sessionContext,
@@ -415,7 +415,7 @@ export async function run(
await Promise.all( await Promise.all(
Object.entries(_state.extensions).map(async ([k, v]) => { Object.entries(_state.extensions).map(async ([k, v]) => {
fault = (await runExtension(_state.sessionContext, k, v)) || fault; fault = (await runExtension(_state.sessionContext, k, v)) || fault;
}) }),
); );
if (fault) { if (fault) {
@@ -435,7 +435,7 @@ export async function run(
sessionContext, sessionContext,
startRevision, startRevision,
maxRevision, maxRevision,
extCounter - _state.extCounter extCounter - _state.extCounter,
); );
} }
+2 -2
View File
@@ -37,7 +37,7 @@ export function variance(deviceId: string, vrnc: number): number {
export function interval( export function interval(
timestamp: number, timestamp: number,
intrvl: number, intrvl: number,
offset = 0 offset = 0,
): number { ): number {
return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset; return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
} }
@@ -52,7 +52,7 @@ export function parseCron(cronExp: string): any {
export function cron( export function cron(
timestamp: number, timestamp: number,
schedule: unknown, schedule: unknown,
offset = 0 offset = 0,
): number[] { ): 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];
+1 -1
View File
@@ -80,7 +80,7 @@ type Promisify<T extends (...args: any) => any> = (
export function start( export function start(
options: ServerOptions, options: ServerOptions,
_listener: Promisify<http.RequestListener> _listener: Promisify<http.RequestListener>,
): void { ): void {
listener = (req, res) => { listener = (req, res) => {
if (stopping) res.setHeader("Connection", "close"); if (stopping) res.setHeader("Connection", "close");
+151 -148
View File
File diff suppressed because it is too large. Load diff
+18 -18
View File
@@ -158,7 +158,7 @@ const getValueType = memoize((str: string) => {
}); });
function parameterValueList( function parameterValueList(
xml: Element xml: Element,
): [Path, string | number | boolean, string][] { ): [Path, string | number | boolean, string][] {
return xml.children return xml.children
.map<[Path, string | number | boolean, string]>((e) => { .map<[Path, string | number | boolean, string]>((e) => {
@@ -291,7 +291,7 @@ function GetParameterNamesResponse(xml): GetParameterNamesResponse {
return { return {
name: "GetParameterNamesResponse", name: "GetParameterNamesResponse",
parameterList: parameterInfoList( parameterList: parameterInfoList(
xml.children.find((n) => n.localName === "ParameterList") xml.children.find((n) => n.localName === "ParameterList"),
), ),
}; };
} }
@@ -308,7 +308,7 @@ function GetParameterValuesResponse(xml: Element): GetParameterValuesResponse {
return { return {
name: "GetParameterValuesResponse", name: "GetParameterValuesResponse",
parameterList: parameterValueList( parameterList: parameterValueList(
xml.children.find((n) => n.localName === "ParameterList") xml.children.find((n) => n.localName === "ParameterList"),
), ),
}; };
} }
@@ -322,12 +322,12 @@ function GetParameterAttributes(methodRequest): string {
} }
function GetParameterAttributesResponse( function GetParameterAttributesResponse(
xml: Element xml: Element,
): GetParameterAttributesResponse { ): 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"),
), ),
}; };
} }
@@ -509,19 +509,19 @@ function Download(methodRequest): string {
}</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 || "0" methodRequest.fileSize || "0"
}</FileSize><TargetFileName>${encodeEntities( }</FileSize><TargetFileName>${encodeEntities(
methodRequest.targetFileName || "" methodRequest.targetFileName || "",
)}</TargetFileName><DelaySeconds>${ )}</TargetFileName><DelaySeconds>${
methodRequest.delaySeconds || "0" methodRequest.delaySeconds || "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>`;
} }
@@ -732,13 +732,13 @@ function RequestDownloadResponse(): string {
function AcsFault(f: CpeFault): string { function AcsFault(f: CpeFault): string {
return `<soap-env:Body:Fault><faultcode>${encodeEntities( return `<soap-env:Body:Fault><faultcode>${encodeEntities(
f.faultCode f.faultCode,
)}</faultcode><faultstring>${encodeEntities( )}</faultcode><faultstring>${encodeEntities(
f.faultString f.faultString,
)}</faultstring><detail><cwmp:Fault><FaultCode>${encodeEntities( )}</faultstring><detail><cwmp:Fault><FaultCode>${encodeEntities(
f.detail.faultCode f.detail.faultCode,
)}</FaultCode><FaultString>${encodeEntities( )}</FaultCode><FaultString>${encodeEntities(
f.detail.faultString f.detail.faultString,
)}</FaultString></cwmp:Fault></detail></soap-env:Body:Fault>`; )}</FaultString></cwmp:Fault></detail></soap-env:Body:Fault>`;
} }
@@ -839,7 +839,7 @@ function fault(xml: Element): CpeFault {
export function request( export function request(
body: string, body: string,
warn: Record<string, unknown>[] warn: Record<string, unknown>[],
): SoapMessage { ): SoapMessage {
warnings = warn; warnings = warn;
@@ -897,7 +897,7 @@ 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");
@@ -1030,7 +1030,7 @@ export function response(rpc: {
throw new Error( throw new Error(
`Unknown method response type ${ `Unknown method response type ${
(rpc.acsResponse as AcsResponse).name (rpc.acsResponse as AcsResponse).name
}` }`,
); );
} }
} else if (rpc.acsRequest) { } else if (rpc.acsRequest) {
@@ -1067,7 +1067,7 @@ export function response(rpc: {
break; break;
default: default:
throw new Error( throw new Error(
`Unknown method request ${(rpc.acsRequest as AcsRequest).name}` `Unknown method request ${(rpc.acsRequest as AcsRequest).name}`,
); );
} }
} else if (rpc.acsFault) { } else if (rpc.acsFault) {
+3 -3
View File
@@ -97,7 +97,7 @@ export type VirtualParameterDeclaration = [
value?: [string | number | boolean, string?]; value?: [string | number | boolean, string?];
notification?: number; notification?: number;
accessList?: string[]; accessList?: string[];
}? }?,
]; ];
export interface SyncState { export interface SyncState {
@@ -139,7 +139,7 @@ export interface SessionContext {
AttributeTimestamps, AttributeTimestamps,
AttributeValues, AttributeValues,
AttributeTimestamps, AttributeTimestamps,
AttributeValues AttributeValues,
][][]; ][][];
revisions: number[]; revisions: number[];
rpcCount: number; rpcCount: number;
@@ -455,7 +455,7 @@ export type Clear = [
notification?: number; notification?: number;
accessList?: number; accessList?: number;
}?, }?,
number? number?,
]; ];
export interface Preset { export interface Preset {
+6 -6
View File
@@ -54,10 +54,10 @@ const getAuthorizer = memoize(
const roles: string[] = JSON.parse(rolesStr); const roles: string[] = JSON.parse(rolesStr);
const allPermissions = localCache.getPermissions(snapshot); const allPermissions = localCache.getPermissions(snapshot);
const permissionSets: PermissionSet[] = roles.map((r) => const permissionSets: PermissionSet[] = roles.map((r) =>
Object.values(allPermissions[r] || {}) Object.values(allPermissions[r] || {}),
); );
return new Authorizer(permissionSets); return new Authorizer(permissionSets);
} },
); );
koa.on("error", (err, ctx) => { koa.on("error", (err, ctx) => {
@@ -90,7 +90,7 @@ koa.use(
return true; return true;
}, },
}) }),
); );
koa.use(async (ctx, next) => { koa.use(async (ctx, next) => {
@@ -110,7 +110,7 @@ koa.use(async (ctx, next) => {
ctx.state.authorizer = getAuthorizer( ctx.state.authorizer = getAuthorizer(
ctx.state.configSnapshot, ctx.state.configSnapshot,
JSON.stringify(roles) JSON.stringify(roles),
); );
return next(); return next();
@@ -245,7 +245,7 @@ router.get("/", async (ctx) => {
window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)}; window.configSnapshot = ${JSON.stringify(ctx.state.configSnapshot)};
window.genieacsVersion = ${JSON.stringify(VERSION)}; window.genieacsVersion = ${JSON.stringify(VERSION)};
window.username = ${JSON.stringify( window.username = ${JSON.stringify(
ctx.state.user ? ctx.state.user.username : "" ctx.state.user ? ctx.state.user.username : "",
)}; )};
window.permissionSets = ${JSON.stringify(permissionSets)}; window.permissionSets = ${JSON.stringify(permissionSets)};
</script> </script>
@@ -269,7 +269,7 @@ koa.use(
[constants.BROTLI_PARAM_QUALITY]: 5, [constants.BROTLI_PARAM_QUALITY]: 5,
}, },
}, },
}) }),
); );
koa.use(router.routes()); koa.use(router.routes());
+9 -9
View File
@@ -104,12 +104,12 @@ router.get(`/devices/:id.csv`, async (ctx) => {
ctx.attachment( ctx.attachment(
`device-${ctx.params.id}-${new Date() `device-${ctx.params.id}-${new Date()
.toISOString() .toISOString()
.replace(/[:.]/g, "")}.csv` .replace(/[:.]/g, "")}.csv`,
); );
ctx.body = new PassThrough(); ctx.body = new PassThrough();
ctx.body.write( ctx.body.write(
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp\n" "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()) {
@@ -259,7 +259,7 @@ for (const [resource, flags] of Object.entries(resources)) {
} }
const columns: Record<string, Expression> = JSON.parse( const columns: Record<string, Expression> = JSON.parse(
singleParam(ctx.request.query.columns) singleParam(ctx.request.query.columns),
); );
const now = Date.now(); const now = Date.now();
@@ -282,11 +282,11 @@ for (const [resource, flags] of Object.entries(resources)) {
ctx.body = new PassThrough(); ctx.body = new PassThrough();
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.write(
Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n" Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n",
); );
for await (const obj of db.query(resource, filter, options)) { for await (const obj of db.query(resource, filter, options)) {
const arr = Object.values(columns).map((exp) => { const arr = Object.values(columns).map((exp) => {
@@ -633,7 +633,7 @@ router.post("/devices/:id/tasks", async (ctx) => {
} }
} }
return exp; return exp;
} },
) as number; ) as number;
const lastInform = device["Events.Inform"].value[0] as number; const lastInform = device["Events.Inform"].value[0] as number;
@@ -643,7 +643,7 @@ router.post("/devices/:id/tasks", async (ctx) => {
const sessionStarted = await apiFunctions.awaitSessionStart( const sessionStarted = await apiFunctions.awaitSessionStart(
deviceId, deviceId,
lastInform, lastInform,
onlineThreshold onlineThreshold,
); );
if (!sessionStarted) { if (!sessionStarted) {
status = "No contact from CPE"; status = "No contact from CPE";
@@ -655,7 +655,7 @@ router.post("/devices/:id/tasks", async (ctx) => {
if (!status) { if (!status) {
const promises = statuses.map((t) => const promises = statuses.map((t) =>
collections.faults.count({ _id: `${deviceId}:task_${t._id}` }) collections.faults.count({ _id: `${deviceId}:task_${t._id}` }),
); );
const res = await Promise.all(promises); const res = await Promise.all(promises);
@@ -746,7 +746,7 @@ router.put("/users/:id/password", async (ctx) => {
!(await apiFunctions.authLocal( !(await apiFunctions.authLocal(
ctx.state.configSnapshot, ctx.state.configSnapshot,
username, username,
ctx.request.body.authPassword ctx.request.body.authPassword,
)) ))
) { ) {
logUnauthorizedWarning(log); logUnauthorizedWarning(log);
+20 -20
View File
@@ -30,7 +30,7 @@ import { Script } from "vm";
import { Readable } from "stream"; import { Readable } from "stream";
function processDeviceProjection( function processDeviceProjection(
projection: Record<string, 1> projection: Record<string, 1>,
): Record<string, 1> { ): Record<string, 1> {
if (!projection) return projection; if (!projection) return projection;
const p = {}; const p = {};
@@ -58,7 +58,7 @@ function processDeviceProjection(
} }
function processDeviceSort( function processDeviceSort(
sort: Record<string, number> sort: Record<string, number>,
): Record<string, number> { ): Record<string, number> {
if (!sort) return sort; if (!sort) return sort;
const s = {}; const s = {};
@@ -93,7 +93,7 @@ interface FlatAttributes {
accessListTimestamp?: number; accessListTimestamp?: number;
} }
interface FlatDevice { export interface FlatDevice {
[param: string]: FlatAttributes; [param: string]: FlatAttributes;
} }
@@ -102,7 +102,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
input, input,
root: string, root: string,
output: FlatDevice, output: FlatDevice,
timestamp: number timestamp: number,
): void { ): void {
for (const [name, tree] of Object.entries(input)) { for (const [name, tree] of Object.entries(input)) {
if (!root) { if (!root) {
@@ -272,7 +272,7 @@ function flattenTask(task: unknown): Task {
} }
function flattenPreset( function flattenPreset(
preset: Record<string, unknown> preset: Record<string, unknown>,
): Record<string, unknown> { ): Record<string, unknown> {
const p = Object.assign({}, preset); const p = Object.assign({}, preset);
if (p.precondition) { if (p.precondition) {
@@ -281,7 +281,7 @@ function flattenPreset(
parse(p.precondition as string); parse(p.precondition as string);
} catch (error) { } catch (error) {
p.precondition = convertOldPrecondition( p.precondition = convertOldPrecondition(
JSON.parse(p.precondition as string) JSON.parse(p.precondition as string),
); );
p.precondition = (p.precondition as string).length p.precondition = (p.precondition as string).length
? stringify(p.precondition as Expression) ? stringify(p.precondition as Expression)
@@ -377,7 +377,7 @@ interface QueryOptions {
export async function* query( export async function* query(
resource: string, resource: string,
filter: Expression, filter: Expression,
options?: QueryOptions options?: QueryOptions,
): AsyncGenerator<any, void, undefined> { ): AsyncGenerator<any, void, undefined> {
options = options || {}; options = options || {};
filter = evaluate(filter, null, Date.now()); filter = evaluate(filter, null, Date.now());
@@ -407,7 +407,7 @@ export async function* query(
.reduce( .reduce(
(obj, [k, v]) => (obj, [k, v]) =>
Object.assign(obj, { [k]: Math.min(Math.max(v, -1), 1) }), Object.assign(obj, { [k]: Math.min(Math.max(v, -1), 1) }),
{} {},
); );
if (resource === "devices") s = processDeviceSort(s); if (resource === "devices") s = processDeviceSort(s);
@@ -435,7 +435,7 @@ export function count(resource: string, filter: Expression): Promise<number> {
export async function updateDeviceTags( export async function updateDeviceTags(
deviceId: string, deviceId: string,
tags: Record<string, boolean> tags: Record<string, boolean>,
): Promise<void> { ): Promise<void> {
const add = []; const add = [];
const pull = []; const pull = [];
@@ -455,7 +455,7 @@ export async function updateDeviceTags(
export async function putPreset( export async function putPreset(
id: string, id: string,
object: Record<string, unknown> object: Record<string, unknown>,
): Promise<void> { ): Promise<void> {
const p = preProcessPreset(object); const p = preProcessPreset(object);
await collections.presets.replaceOne({ _id: id }, p, { upsert: true }); await collections.presets.replaceOne({ _id: id }, p, { upsert: true });
@@ -467,7 +467,7 @@ export async function deletePreset(id: string): Promise<void> {
export async function putProvision( export async function putProvision(
id: string, id: string,
object: { script: string } object: { script: string },
): Promise<void> { ): Promise<void> {
if (!object.script) object.script = ""; if (!object.script) object.script = "";
try { try {
@@ -478,7 +478,7 @@ export async function putProvision(
} catch (err) { } catch (err) {
if (err.stack?.startsWith(`${id}:`)) { if (err.stack?.startsWith(`${id}:`)) {
return Promise.reject( return Promise.reject(
new Error(`${err.name} at ${err.stack.split("\n", 1)[0]}`) new Error(`${err.name} at ${err.stack.split("\n", 1)[0]}`),
); );
} }
return Promise.reject(err); return Promise.reject(err);
@@ -494,7 +494,7 @@ export async function deleteProvision(id: string): Promise<void> {
export async function putVirtualParameter( export async function putVirtualParameter(
id: string, id: string,
object: { script: string } object: { script: string },
): Promise<void> { ): Promise<void> {
if (!object.script) object.script = ""; if (!object.script) object.script = "";
try { try {
@@ -505,7 +505,7 @@ export async function putVirtualParameter(
} catch (err) { } catch (err) {
if (err.stack?.startsWith(`${id}:`)) { if (err.stack?.startsWith(`${id}:`)) {
return Promise.reject( return Promise.reject(
new Error(`${err.name} at ${err.stack.split("\n", 1)[0]}`) new Error(`${err.name} at ${err.stack.split("\n", 1)[0]}`),
); );
} }
return Promise.reject(err); return Promise.reject(err);
@@ -521,7 +521,7 @@ export async function deleteVirtualParameter(id: string): Promise<void> {
export async function putConfig( export async function putConfig(
id: string, id: string,
object: WithoutId<MongoTypes.Config> object: WithoutId<MongoTypes.Config>,
): Promise<void> { ): Promise<void> {
await collections.config.replaceOne({ _id: id }, object, { upsert: true }); await collections.config.replaceOne({ _id: id }, object, { upsert: true });
} }
@@ -532,7 +532,7 @@ export async function deleteConfig(id: string): Promise<void> {
export async function putPermission( export async function putPermission(
id: string, id: string,
object: WithoutId<MongoTypes.Permission> object: WithoutId<MongoTypes.Permission>,
): Promise<void> { ): Promise<void> {
await collections.permissions.replaceOne({ _id: id }, object, { await collections.permissions.replaceOne({ _id: id }, object, {
upsert: true, upsert: true,
@@ -545,13 +545,13 @@ export async function deletePermission(id: string): Promise<void> {
export async function putUser( export async function putUser(
id: string, id: string,
object: Partial<WithoutId<MongoTypes.User>> object: Partial<WithoutId<MongoTypes.User>>,
): Promise<void> { ): Promise<void> {
// 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
await collections.users.updateOne( await collections.users.updateOne(
{ _id: id }, { _id: id },
{ $set: object }, { $set: object },
{ upsert: true } { upsert: true },
); );
} }
@@ -566,7 +566,7 @@ export function downloadFile(filename: string): Readable {
export function putFile( export function putFile(
filename: string, filename: string,
metadata: Record<string, string>, metadata: Record<string, string>,
contentStream: Readable contentStream: Readable,
): Promise<void> { ): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const uploadStream = filesBucket.openUploadStreamWithId( const uploadStream = filesBucket.openUploadStreamWithId(
@@ -574,7 +574,7 @@ export function putFile(
filename, filename,
{ {
metadata: metadata, metadata: metadata,
} },
); );
let readableEnded = false; let readableEnded = false;
+1 -1
View File
@@ -125,7 +125,7 @@ export function getConfig(
key: string, key: string,
context: Record<string, unknown>, context: Record<string, unknown>,
now: number, now: number,
cb?: (e: Expression) => Expression cb?: (e: Expression) => Expression,
): string | number | boolean | null { ): string | number | boolean | null {
const snapshot = localCache.get(revision); const snapshot = localCache.get(revision);
if (!snapshot) throw new Error("Cache snapshot does not exist"); if (!snapshot) throw new Error("Cache snapshot does not exist");
+3 -3
View File
@@ -20,7 +20,7 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
export function generateDeviceId( export function generateDeviceId(
deviceIdStruct: Record<string, string> deviceIdStruct: Record<string, string>,
): string { ): string {
// Escapes everything except alphanumerics and underscore // Escapes everything except alphanumerics and underscore
function esc(str): string { function esc(str): string {
@@ -54,7 +54,7 @@ export function encodeTag(tag: string): string {
return encodeURIComponent(tag) return encodeURIComponent(tag)
.replace( .replace(
/[!~*'().]/g, /[!~*'().]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase() (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
) )
.replace(/0x(?=[0-9A-Z]{2})/g, "0%78") .replace(/0x(?=[0-9A-Z]{2})/g, "0%78")
.replace(/%/g, "0x"); .replace(/%/g, "0x");
@@ -67,7 +67,7 @@ export function decodeTag(tag: string): string {
export function once( export function once(
emitter: EventEmitter, emitter: EventEmitter,
event: string, event: string,
timeout: number timeout: number,
): Promise<unknown[]> { ): Promise<unknown[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
+1 -1
View File
@@ -138,7 +138,7 @@ export default class VersionedMap<K, V> {
const del = revisionsObj.delete || 0; const del = revisionsObj.delete || 0;
const mutations = Object.keys(revisionsObj).reduce( const mutations = Object.keys(revisionsObj).reduce(
(acc, cur) => (cur === "delete" ? acc : acc | (1 << +cur)), (acc, cur) => (cur === "delete" ? acc : acc | (1 << +cur)),
del del,
); );
const revisions = []; const revisions = [];
+2600 -3072
View File
File diff suppressed because it is too large. Load diff
+13 -16
View File
@@ -35,28 +35,28 @@
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/codemirror": "^5.60.5", "@types/codemirror": "^5.60.15",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"@types/koa": "^2.13.5", "@types/koa": "^2.13.5",
"@types/koa-compress": "^4.0.3", "@types/koa-compress": "^4.0.3",
"@types/koa-router": "^7.4.4", "@types/koa-router": "^7.4.4",
"@types/koa-static": "^4.0.2", "@types/koa-static": "^4.0.2",
"@types/mithril": "^2.0.11", "@types/mithril": "^2.2.6",
"@types/node": "^18.7.9", "@types/node": "^20.10.4",
"@types/parsimmon": "^1.10.6", "@types/parsimmon": "^1.10.9",
"@types/seedrandom": "^3.0.2", "@types/seedrandom": "^3.0.8",
"@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/parser": "^6.14.0",
"ava": "^3.15.0", "ava": "^3.15.0",
"codemirror": "^5.65.8", "codemirror": "^5.65.16",
"esbuild": "^0.19.9", "esbuild": "^0.19.9",
"eslint": "^8.22.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"mithril": "^2.2.2", "mithril": "^2.2.2",
"prettier": "^2.7.1", "prettier": "^3.1.1",
"sql.js": "^1.7.0", "sql.js": "^1.7.0",
"svgo": "^2.8.0", "svgo": "^2.8.0",
"typescript": "^4.7.4", "typescript": "^5.3.3",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },
"engines": { "engines": {
@@ -64,10 +64,7 @@
}, },
"scripts": { "scripts": {
"test": "esbuild --log-level=warning --bundle --platform=node --target=node12 --packages=external --outdir=test test/*.ts && ava --verbose --color && rm test/*.js", "test": "esbuild --log-level=warning --bundle --platform=node --target=node12 --packages=external --outdir=test test/*.ts && ava --verbose --color && rm test/*.js",
"lint-eslint": "eslint --fix \"bin/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\"", "lint": "esbuild build/lint.ts --bundle --platform=node --target=node16 --packages=external | node",
"lint-prettier": "prettier --prose-wrap always --write .",
"lint-tsc": "tsc --noEmit",
"lint": "npm run lint-eslint && npm run lint-prettier && npm run lint-tsc",
"build": "esbuild build/build.ts --bundle --platform=node --target=node12 --packages=external | node" "build": "esbuild build/build.ts --bundle --platform=node --target=node12 --packages=external | node"
} }
} }
+3 -3
View File
@@ -27,7 +27,7 @@ ava("digest", (t) => {
uri, uri,
method, method,
body, body,
wwwAuthHeader wwwAuthHeader,
); );
const authHeader = auth.parseAuthorizationHeader(solution); const authHeader = auth.parseAuthorizationHeader(solution);
t.is( t.is(
@@ -42,8 +42,8 @@ ava("digest", (t) => {
authHeader["qop"], authHeader["qop"],
body, body,
authHeader["cnonce"], authHeader["cnonce"],
authHeader["nc"] authHeader["nc"],
) ),
); );
} }
}); });
+1 -1
View File
@@ -55,7 +55,7 @@ ava("convertOldPrecondition", (t) => {
for (const test of tests) { for (const test of tests) {
t.is( t.is(
stringify(convertOldPrecondition(test[0] as Record<string, unknown>)), stringify(convertOldPrecondition(test[0] as Record<string, unknown>)),
test[1] test[1],
); );
} }
+1 -1
View File
@@ -85,7 +85,7 @@ ava("unpack", (t) => {
let unpacked: Path[]; let unpacked: Path[];
unpacked = device.unpack( unpacked = device.unpack(
deviceData, deviceData,
Path.parse("a.[b:b,c:c].a.[b:b1,c:c1].a") Path.parse("a.[b:b,c:c].a.[b:b1,c:c1].a"),
); );
t.is(unpacked.length, 2); t.is(unpacked.length, 2);
t.is(unpacked[0].toString(), "a.1.a.1.a"); t.is(unpacked[0].toString(), "a.1.a.1.a");
+6 -6
View File
@@ -38,31 +38,31 @@ ava("find", (t) => {
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", "*.*"],
); );
}); });
+4 -4
View File
@@ -21,7 +21,7 @@ test.before(async (t) => {
}; };
db.run( db.run(
"CREATE TABLE test (id INTEGER PRIMARY KEY, string STRING, decimal DECIMAL(4,2))" "CREATE TABLE test (id INTEGER PRIMARY KEY, string STRING, decimal DECIMAL(4,2))",
); );
const stmt = db.prepare("INSERT INTO test (string, decimal) VALUES (?, ?)"); const stmt = db.prepare("INSERT INTO test (string, decimal) VALUES (?, ?)");
@@ -72,16 +72,16 @@ test("minimize", async (t) => {
STRING_VALUES.filter((s) => s), STRING_VALUES.filter((s) => s),
STRING_VALUES.filter((s) => s), STRING_VALUES.filter((s) => s),
[">", "=", "<"], [">", "=", "<"],
["<>", ">="] ["<>", ">="],
)) { )) {
cases.push( cases.push(
`string ${op1} "${s1}" OR string ='${s2}' OR NOT string ${op2} '${s3}'` `string ${op1} "${s1}" OR string ='${s2}' OR NOT string ${op2} '${s3}'`,
); );
} }
for (const [s1, s2] of getPermutations( for (const [s1, s2] of getPermutations(
STRING_VALUES.filter((s) => s), STRING_VALUES.filter((s) => s),
STRING_VALUES.filter((s) => s) STRING_VALUES.filter((s) => s),
)) ))
cases.push(`string > "${s1}" AND string < '${s2}'`); cases.push(`string > "${s1}" AND string < '${s2}'`);
+1 -1
View File
@@ -36,6 +36,6 @@ ava("generateDeviceId", (t) => {
ava("escapeRegExp", (t) => { ava("escapeRegExp", (t) => {
t.is( t.is(
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"), common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
"\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]" "\\\\ \\^ \\$ \\* \\+ \\? \\. \\( \\) \\| \\{ \\} \\[ \\]",
); );
}); });
+2 -2
View File
@@ -7,7 +7,7 @@ import {
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 />',
); );
const attrs = parseXmlDeclaration(buf); const attrs = parseXmlDeclaration(buf);
t.deepEqual(attrs, [ t.deepEqual(attrs, [
@@ -29,7 +29,7 @@ ava("parseXmlDeclaration", (t) => {
ava("decodeEntities", (t) => { ava("decodeEntities", (t) => {
t.is( t.is(
decodeEntities("&&amp;&lt;&gt;&quot;&apos;&gt;&#167;&#xd842;&#xDFB7;;"), decodeEntities("&&amp;&lt;&gt;&quot;&apos;&gt;&#167;&#xd842;&#xDFB7;;"),
"&&<>\"'>§𠮷;" "&&<>\"'>§𠮷;",
); );
}); });
+1 -1
View File
@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"lib": ["ES2022", "dom"], "lib": ["ES2022", "dom"],
"module": "ES2022", "module": "ES2022",
"moduleResolution": "node", "moduleResolution": "bundler",
"target": "ES2022", "target": "ES2022",
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
+18 -14
View File
@@ -31,8 +31,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["presets"] }, { class: active["presets"] },
m("a", { href: "#!/admin/presets" }, "Presets") m("a", { href: "#!/admin/presets" }, "Presets"),
) ),
); );
} }
@@ -41,8 +41,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["provisions"] }, { class: active["provisions"] },
m("a", { href: "#!/admin/provisions" }, "Provisions") m("a", { href: "#!/admin/provisions" }, "Provisions"),
) ),
); );
} }
@@ -51,8 +51,12 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["virtualParameters"] }, { class: active["virtualParameters"] },
m("a", { href: "#!/admin/virtualParameters" }, "Virtual Parameters") m(
) "a",
{ href: "#!/admin/virtualParameters" },
"Virtual Parameters",
),
),
); );
} }
@@ -61,8 +65,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["files"] }, { class: active["files"] },
m("a", { href: "#!/admin/files" }, "Files") m("a", { href: "#!/admin/files" }, "Files"),
) ),
); );
} }
@@ -71,8 +75,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["config"] }, { class: active["config"] },
m("a", { href: "#!/admin/config" }, "Config") m("a", { href: "#!/admin/config" }, "Config"),
) ),
); );
} }
@@ -81,8 +85,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["permissions"] }, { class: active["permissions"] },
m("a", { href: "#!/admin/permissions" }, "Permissions") m("a", { href: "#!/admin/permissions" }, "Permissions"),
) ),
); );
} }
@@ -91,8 +95,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["users"] }, { class: active["users"] },
m("a", { href: "#!/admin/users" }, "Users") m("a", { href: "#!/admin/users" }, "Users"),
) ),
); );
} }
+1 -1
View File
@@ -137,7 +137,7 @@ m.route(document.body, "/overview", {
"/admin/provisions": pagify("provisions", provisionsPage), "/admin/provisions": pagify("provisions", provisionsPage),
"/admin/virtualParameters": pagify( "/admin/virtualParameters": pagify(
"virtualParameters", "virtualParameters",
virtualParametersPage virtualParametersPage,
), ),
"/admin/files": pagify("files", filesPage), "/admin/files": pagify("files", filesPage),
"/admin/config": pagify("config", configPage), "/admin/config": pagify("config", configPage),
+3 -3
View File
@@ -19,7 +19,7 @@
type AutocompleteCallback = ( type AutocompleteCallback = (
value: string, value: string,
callback: (suggestions: { value: string; tip?: string }[]) => void callback: (suggestions: { value: string; tip?: string }[]) => void,
) => void; ) => void;
export default class Autocomplete { export default class Autocomplete {
@@ -180,14 +180,14 @@ export default class Autocomplete {
if (selectedElement) { if (selectedElement) {
this.container.scrollTop = Math.min( this.container.scrollTop = Math.min(
this.container.scrollTop, this.container.scrollTop,
selectedElement.offsetTop selectedElement.offsetTop,
); );
this.container.scrollTop = Math.max( this.container.scrollTop = Math.max(
this.container.scrollTop, this.container.scrollTop,
selectedElement.offsetTop + selectedElement.offsetTop +
selectedElement.scrollHeight - selectedElement.scrollHeight -
this.container.clientHeight this.container.clientHeight,
); );
} }
}); });
+8 -8
View File
@@ -52,7 +52,7 @@ const component: ClosureComponent<Attrs> = () => {
oncreate: (_vnode) => { oncreate: (_vnode) => {
(_vnode.dom as HTMLSelectElement).focus(); (_vnode.dom as HTMLSelectElement).focus();
}, },
}) }),
), ),
]; ];
@@ -75,8 +75,8 @@ const component: ClosureComponent<Attrs> = () => {
oninput: (e) => { oninput: (e) => {
vnode.state[f] = e.target.value; vnode.state[f] = e.target.value;
}, },
}) }),
) ),
); );
} }
@@ -85,7 +85,7 @@ const component: ClosureComponent<Attrs> = () => {
{ {
type: "submit", type: "submit",
}, },
"Change password" "Change password",
) as VnodeDOM; ) as VnodeDOM;
form.push(m(".actions-bar", submit)); form.push(m(".actions-bar", submit));
@@ -109,19 +109,19 @@ const component: ClosureComponent<Attrs> = () => {
) { ) {
notifications.push( notifications.push(
"error", "error",
"Password confirm doesn't match new password" "Password confirm doesn't match new password",
); );
} else { } else {
(submit.dom as HTMLFormElement).disabled = true; (submit.dom as HTMLFormElement).disabled = true;
changePassword( changePassword(
vnode.state["username"], vnode.state["username"],
vnode.state["newPassword"], vnode.state["newPassword"],
vnode.state["authPassword"] vnode.state["authPassword"],
) )
.then(() => { .then(() => {
notifications.push( notifications.push(
"success", "success",
"Password updated successfully" "Password updated successfully",
); );
if (onPasswordChange) onPasswordChange(); if (onPasswordChange) onPasswordChange();
(submit.dom as HTMLFormElement).disabled = false; (submit.dom as HTMLFormElement).disabled = false;
@@ -133,7 +133,7 @@ const component: ClosureComponent<Attrs> = () => {
} }
}, },
}, },
form form,
), ),
]; ];
+1 -1
View File
@@ -54,7 +54,7 @@ const component: ClosureComponent<Attrs> = () => {
if (vnode.attrs.onSubmit) vnode.attrs.onSubmit(_vnode.dom); if (vnode.attrs.onSubmit) vnode.attrs.onSubmit(_vnode.dom);
}, },
}, },
} },
); );
if (vnode.attrs.onChange) { if (vnode.attrs.onChange) {
+6 -5
View File
@@ -63,10 +63,11 @@ const vnodeContext = new WeakMap<Vnode, Attributes>();
interface MC extends Static { interface MC extends Static {
context: { context: {
(ctx: Attributes, selector: string, ...children: Children[]): Vnode< (
any, ctx: Attributes,
any selector: string,
>; ...children: Children[]
): Vnode<any, any>;
( (
ctx: Attributes, ctx: Attributes,
selector: string, selector: string,
@@ -150,7 +151,7 @@ export function contextifyComponent(component: ComponentTypes): ComponentTypes {
title: "Click to print stack trace to console", title: "Click to print stack trace to console",
onclick: () => console.error(err), onclick: () => console.error(err),
}, },
"Error!" "Error!",
); );
} }
}; };
+33 -24
View File
@@ -17,14 +17,16 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ClosureComponent, Component } from "mithril"; import { ClosureComponent } from "mithril";
import { m } from "../components"; import { m } from "../components";
import * as taskQueue from "../task-queue"; import * as taskQueue from "../task-queue";
import { parse } from "../../lib/common/expression/parser"; import { parse } from "../../lib/common/expression/parser";
import memoize from "../../lib/common/memoize"; import memoize from "../../lib/common/memoize";
import { getIcon } from "../icons"; import { getIcon } from "../icons";
import { evaluateExpression } from "../store"; import { QueryResponse, evaluateExpression } from "../store";
import debounce from "../../lib/common/debounce"; import debounce from "../../lib/common/debounce";
import { Expression } from "../../lib/types";
import { FlatDevice } from "../../lib/ui/db";
const memoizedParse = memoize(parse); const memoizedParse = memoize(parse);
@@ -52,7 +54,13 @@ function orderKeysByDepth(device: Record<string, unknown>): string[][] {
return res; return res;
} }
const component: ClosureComponent = (): Component => { interface Attrs {
device: FlatDevice;
limit: Expression;
deviceQuery: QueryResponse;
}
const component: ClosureComponent<Attrs> = () => {
let queryString: string; let queryString: string;
const formQueryString = debounce((args: string[]) => { const formQueryString = debounce((args: string[]) => {
queryString = args[args.length - 1]; queryString = args[args.length - 1];
@@ -61,9 +69,10 @@ const component: ClosureComponent = (): Component => {
return { return {
view: (vnode) => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs.device;
const limit = evaluateExpression(vnode.attrs["limit"], device) || 100; const limit =
(evaluateExpression(vnode.attrs.limit, device) as number) || 100;
const search = m("input", { const search = m("input", {
type: "text", type: "text",
@@ -108,8 +117,8 @@ const component: ClosureComponent = (): Component => {
val.push( val.push(
m( m(
"parameter", "parameter",
Object.assign({ device: device, parameter: memoizedParse(k) }) Object.assign({ device: device, parameter: memoizedParse(k) }),
) ),
); );
} else if (p.object && p.writable) { } else if (p.object && p.writable) {
if (instanceRegex.test(k)) { if (instanceRegex.test(k)) {
@@ -121,13 +130,13 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "deleteObject", name: "deleteObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0] as string,
objectName: k, objectName: k,
}); });
}, },
}, },
getIcon("delete-instance") getIcon("delete-instance"),
) ),
); );
} else { } else {
val.push( val.push(
@@ -138,13 +147,13 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "addObject", name: "addObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0] as string,
objectName: k, objectName: k,
}); });
}, },
}, },
getIcon("add-instance") getIcon("add-instance"),
) ),
); );
} }
} }
@@ -157,38 +166,38 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "getParameterValues", name: "getParameterValues",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0] as string,
parameterNames: [k], parameterNames: [k],
}); });
}, },
}, },
getIcon("refresh") getIcon("refresh"),
) ),
); );
return m( return m(
"tr", "tr",
attrs, attrs,
m("td.left", m("long-text", { text: k })), m("td.left", m("long-text", { text: k })),
m("td.right", val) m("td.right", val),
); );
}); });
return m( return m(
"loading", "loading",
{ queries: [vnode.attrs["deviceQuery"]] }, { queries: [vnode.attrs.deviceQuery] },
m( 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(
@@ -196,10 +205,10 @@ const component: ClosureComponent = (): Component => {
m("table", m("tbody", rows)), m("table", m("tbody", rows)),
m( m(
"m", "m",
`Displaying ${filteredKeys.length} out of ${count} parameters.` `Displaying ${filteredKeys.length} out of ${count} parameters.`,
) ),
) ),
) ),
); );
}, },
}; };
+15 -9
View File
@@ -17,18 +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 { Attributes, ClosureComponent, Component } from "mithril"; import { Attributes, ClosureComponent } from "mithril";
import { map } from "../../lib/common/expression/parser"; import { map } from "../../lib/common/expression/parser";
import memoize from "../../lib/common/memoize"; import memoize from "../../lib/common/memoize";
import { Expression } from "../../lib/types"; import { Expression } from "../../lib/types";
import { m } from "../components"; import { m } from "../components";
import { evaluateExpression, getTimestamp } from "../store"; import { evaluateExpression, getTimestamp } from "../store";
import { FlatDevice } from "../../lib/ui/db";
const evaluateAttributes = memoize( const evaluateAttributes = memoize(
( (
attrs: Record<string, Expression>, attrs: Record<string, Expression>,
obj: Record<string, unknown>, obj: Record<string, unknown>,
now: number // eslint-disable-line @typescript-eslint/no-unused-vars now: number, // eslint-disable-line @typescript-eslint/no-unused-vars
): Attributes => { ): Attributes => {
const res: Attributes = {}; const res: Attributes = {};
for (const [k, v] of Object.entries(attrs)) { for (const [k, v] of Object.entries(attrs)) {
@@ -47,19 +48,24 @@ const evaluateAttributes = memoize(
res[k] = evaluateExpression(vv, obj); res[k] = evaluateExpression(vv, obj);
} }
return res; return res;
} },
); );
const component: ClosureComponent = (): Component => { interface Attrs {
device: FlatDevice;
filter: Expression;
components: unknown;
}
const component: ClosureComponent<Attrs> = () => {
return { return {
view: (vnode) => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs.device;
if ("filter" in vnode.attrs) { if ("filter" in vnode.attrs) {
if (!evaluateExpression(vnode.attrs["filter"], device || {})) if (!evaluateExpression(vnode.attrs.filter, device || {})) return null;
return null;
} }
const children = Object.values(vnode.attrs["components"]).map((c) => { const children = Object.values(vnode.attrs.components).map((c) => {
if (Array.isArray(c)) c = evaluateExpression(c, device || {}); if (Array.isArray(c)) c = evaluateExpression(c, device || {});
if (typeof c !== "object") return `${c}`; if (typeof c !== "object") return `${c}`;
const type = evaluateExpression(c["type"], device || {}); const type = evaluateExpression(c["type"], device || {});
@@ -79,7 +85,7 @@ const component: ClosureComponent = (): Component => {
attrs = evaluateAttributes( attrs = evaluateAttributes(
el["attributes"], el["attributes"],
device || {}, device || {},
getTimestamp() getTimestamp(),
); );
} }
+8 -8
View File
@@ -42,8 +42,8 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Reboot" "Reboot",
) ),
); );
buttons.push( buttons.push(
@@ -58,8 +58,8 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Reset" "Reset",
) ),
); );
buttons.push( buttons.push(
@@ -74,8 +74,8 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Push file" "Push file",
) ),
); );
buttons.push( buttons.push(
@@ -98,8 +98,8 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Delete" "Delete",
) ),
); );
return m(".actions-bar", buttons); return m(".actions-bar", buttons);
+5 -5
View File
@@ -76,8 +76,8 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
getIcon("remove") getIcon("remove"),
) ),
), ),
]); ]);
} }
@@ -86,19 +86,19 @@ const component: ClosureComponent = (): Component => {
if (rows.length) { if (rows.length) {
tbody = m( tbody = m(
"tbody", "tbody",
rows.map((r) => m("tr", r)) rows.map((r) => m("tr", r)),
); );
} else { } else {
tbody = m( tbody = m(
"tbody", "tbody",
m("tr.empty", m("td", { colspan: headers.length }, "No faults")) m("tr.empty", m("td", { colspan: headers.length }, "No faults")),
); );
} }
return m( return m(
"loading", "loading",
{ queries: [faults] }, { queries: [faults] },
m("table.table", thead, tbody) m("table.table", thead, tbody),
); );
}, },
}; };
+1 -1
View File
@@ -40,7 +40,7 @@ const component: ClosureComponent = (): Component => {
return m( return m(
"a", "a",
{ href: `#!/devices/${encodeURIComponent(deviceId)}` }, { href: `#!/devices/${encodeURIComponent(deviceId)}` },
children children,
); );
} else { } else {
return children; return children;
+1 -1
View File
@@ -47,7 +47,7 @@ const component: ClosureComponent = (): Component => {
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"]}`);
} }
+22 -11
View File
@@ -17,24 +17,35 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ClosureComponent, Component, VnodeDOM } from "mithril"; import { ClosureComponent, VnodeDOM } from "mithril";
import { m } from "../components"; import { m } from "../components";
import { evaluateExpression } from "../store"; import { QueryResponse, evaluateExpression } from "../store";
import { FlatDevice } from "../../lib/ui/db";
import { Expression } from "../../lib/types";
const component: ClosureComponent = (): Component => { interface Attrs {
device: FlatDevice;
parameters: Record<
string,
{ type?: Expression; label: string; parameter: Expression }
>;
deviceQuery: QueryResponse;
}
const component: ClosureComponent<Attrs> = () => {
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 type = evaluateExpression(parameter["type"], device); const type = evaluateExpression(parameter.type, device);
const p = m.context( const p = m.context(
{ {
device: device, device: device,
parameter: parameter["parameter"], parameter: parameter.parameter,
}, },
(type as string) || "parameter", (type as string) || "parameter",
parameter parameter,
); );
return m( return m(
@@ -52,14 +63,14 @@ const component: ClosureComponent = (): Component => {
}, },
}, },
m("th", parameter["label"]), m("th", parameter["label"]),
m("td", p) m("td", p),
); );
}); });
return m( return m(
"loading", "loading",
{ queries: [vnode.attrs["deviceQuery"]] }, { queries: [vnode.attrs.deviceQuery] },
m("table.parameter-list", rows) m("table.parameter-list", rows),
); );
}, },
}; };
+33 -22
View File
@@ -17,24 +17,35 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ClosureComponent, Component } from "mithril"; import { ClosureComponent } from "mithril";
import { m } from "../components"; import { m } from "../components";
import * as taskQueue from "../task-queue"; import * as taskQueue from "../task-queue";
import { evaluateExpression } from "../store"; import { QueryResponse, evaluateExpression } from "../store";
import * as expressionParser from "../../lib/common/expression/parser"; import * as expressionParser from "../../lib/common/expression/parser";
import { getIcon } from "../icons"; import { getIcon } from "../icons";
import { FlatDevice } from "../../lib/ui/db";
import { Expression } from "../../lib/types";
const component: ClosureComponent = (): Component => { interface Attrs {
device: FlatDevice;
parameter: Expression;
label: Expression;
childParameters: Record<string, { label: string; parameter: Expression }>;
filter?: Expression;
deviceQuery: QueryResponse;
}
const component: ClosureComponent<Attrs> = () => {
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 = evaluateExpression(vnode.state["object"], device); const object = evaluateExpression(vnode.state["object"], device);
const parameters = vnode.state["parameters"]; const parameters = vnode.state["parameters"];
@@ -56,7 +67,7 @@ const component: ClosureComponent = (): Component => {
const rows = []; const rows = [];
for (const i of instances) { for (const i of instances) {
let filter = "filter" in vnode.attrs ? vnode.attrs["filter"] : true; let filter = "filter" in vnode.attrs ? 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")
@@ -84,8 +95,8 @@ const component: ClosureComponent = (): Component => {
device: device, device: device,
parameter: param, parameter: param,
label: null, label: null,
}) }),
) ),
); );
}); });
@@ -100,14 +111,14 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "deleteObject", name: "deleteObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0] as string,
objectName: i, objectName: i,
}); });
}, },
}, },
getIcon("delete-instance") getIcon("delete-instance"),
) ),
) ),
); );
} }
rows.push(m("tr", row)); rows.push(m("tr", row));
@@ -115,7 +126,7 @@ const component: ClosureComponent = (): Component => {
if (!rows.length) { if (!rows.length) {
rows.push( rows.push(
m("tr.empty", m("td", { colspan: headers.length }, "No instances")) m("tr.empty", m("td", { colspan: headers.length }, "No instances")),
); );
} }
@@ -133,29 +144,29 @@ const component: ClosureComponent = (): Component => {
onclick: () => { onclick: () => {
taskQueue.queueTask({ taskQueue.queueTask({
name: "addObject", name: "addObject",
device: device["DeviceID.ID"].value[0], device: device["DeviceID.ID"].value[0] as string,
objectName: object, objectName: object,
}); });
}, },
}, },
getIcon("add-instance") getIcon("add-instance"),
) ),
) ),
) ),
); );
} }
let label; let label;
const l = evaluateExpression(vnode.attrs["label"], device); const l = evaluateExpression(vnode.attrs.label, device);
if (l != null) label = m("h2", l); if (l != null) label = m("h2", l);
return [ return [
label, label,
m( m(
"loading", "loading",
{ queries: [vnode.attrs["deviceQuery"]] }, { queries: [vnode.attrs.deviceQuery] },
m("table.table", thead, m("tbody", rows)) m("table.table", thead, m("tbody", rows)),
), ),
]; ];
}, },
+3 -3
View File
@@ -78,7 +78,7 @@ const component: ClosureComponent = (): Component => {
const { value, timestamp, parameter } = evaluateParam( const { value, timestamp, parameter } = evaluateParam(
vnode.attrs["parameter"], vnode.attrs["parameter"],
device, device,
store.getTimestamp() + store.getClockSkew() store.getTimestamp() + store.getClockSkew(),
); );
if (value == null) return null; if (value == null) return null;
@@ -103,7 +103,7 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
getIcon("edit") getIcon("edit"),
); );
} }
@@ -124,7 +124,7 @@ const component: ClosureComponent = (): Component => {
}, },
}, },
el, el,
edit edit,
); );
}, },
}; };
+4 -4
View File
@@ -62,19 +62,19 @@ const component: ClosureComponent = (): Component => {
if (connectionRequestStatus !== "OK") { if (connectionRequestStatus !== "OK") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${connectionRequestStatus}` `${deviceId}: ${connectionRequestStatus}`,
); );
} else if (tasks2[0].status === "stale") { } else if (tasks2[0].status === "stale") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: No contact from device` `${deviceId}: No contact from device`,
); );
} else if (tasks2[0].status === "fault") { } else if (tasks2[0].status === "fault") {
notifications.push("error", `${deviceId}: Refresh faulted`); notifications.push("error", `${deviceId}: Refresh faulted`);
} else { } else {
notifications.push("success", `${deviceId}: Summoned`); notifications.push("success", `${deviceId}: Summoned`);
} }
} },
) )
.then(() => { .then(() => {
e.target.disabled = false; e.target.disabled = false;
@@ -86,7 +86,7 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Summon" "Summon",
); );
}, },
}; };
+22 -15
View File
@@ -17,20 +17,27 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ClosureComponent, Component } from "mithril"; import { ClosureComponent } from "mithril";
import { m } from "../components"; import { m } from "../components";
import * as notifications from "../notifications"; import * as notifications from "../notifications";
import * as store from "../store"; import * as store from "../store";
import { getIcon } from "../icons"; import { getIcon } from "../icons";
import { decodeTag } from "../../lib/util"; import { decodeTag } from "../../lib/util";
import { Expression } from "../../lib/types";
import { FlatDevice } from "../../lib/ui/db";
const component: ClosureComponent = (): Component => { interface Attrs {
device: FlatDevice;
writable?: Expression;
}
const component: ClosureComponent<Attrs> = () => {
return { return {
view: (vnode) => { view: (vnode) => {
const device = vnode.attrs["device"]; const device = vnode.attrs.device;
let writable = true; let writable = true;
if ("writable" in vnode.attrs) if ("writable" in vnode.attrs)
writable = !!store.evaluateExpression(vnode.attrs["writable"], device); writable = !!store.evaluateExpression(vnode.attrs.writable, device);
const tags = []; const tags = [];
for (const p of Object.keys(device)) for (const p of Object.keys(device))
@@ -41,7 +48,7 @@ const component: ClosureComponent = (): Component => {
if (!writable) { if (!writable) {
return m( return m(
".tags", ".tags",
tags.map((t) => m("span.tag", t)) tags.map((t) => m("span.tag", t)),
); );
} }
@@ -56,14 +63,14 @@ const component: ClosureComponent = (): Component => {
{ {
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] as string;
store store
.updateTags(deviceId, { [tag]: false }) .updateTags(deviceId, { [tag]: false })
.then(() => { .then(() => {
e.target.disabled = false; e.target.disabled = false;
notifications.push( notifications.push(
"success", "success",
`${deviceId}: Tags updated` `${deviceId}: Tags updated`,
); );
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
}) })
@@ -71,14 +78,14 @@ const component: ClosureComponent = (): Component => {
e.target.disabled = false; e.target.disabled = false;
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${err.message}` `${deviceId}: ${err.message}`,
); );
}); });
}, },
}, },
getIcon("remove") getIcon("remove"),
) ),
) ),
), ),
m( m(
"span.tag.writable", "span.tag.writable",
@@ -88,7 +95,7 @@ const component: ClosureComponent = (): Component => {
{ {
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] as string;
const tag = prompt(`Enter tag to assign to device:`); const tag = prompt(`Enter tag to assign to device:`);
if (!tag) { if (!tag) {
e.target.disabled = false; e.target.disabled = false;
@@ -108,9 +115,9 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
getIcon("add") getIcon("add"),
) ),
) ),
); );
}, },
}; };
+1 -1
View File
@@ -129,7 +129,7 @@ export function structureConfig(config: Config[]): any {
export function diffConfig( export function diffConfig(
current: Record<string, unknown>, current: Record<string, unknown>,
target: Record<string, unknown> target: Record<string, unknown>,
): Diff { ): Diff {
const diff = { const diff = {
add: [], add: [],
+24 -24
View File
@@ -74,7 +74,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
.then(() => { .then(() => {
notifications.push( notifications.push(
"success", "success",
`Config ${exists ? "updated" : "created"}` `Config ${exists ? "updated" : "created"}`,
); );
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
resolve(null); resolve(null);
@@ -112,7 +112,7 @@ function escapeRegExp(str): string {
export function init(): Promise<Record<string, unknown>> { export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("config", 2)) { if (!window.authorizer.hasAccess("config", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page"),
); );
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -173,19 +173,19 @@ function renderTable(confsResponse, searchString): Children {
}); });
}, },
}, },
formData formData,
) ),
); );
cb = () => comp; cb = () => comp;
overlay.open( overlay.open(
cb, cb,
() => () =>
!comp.state["current"]["modified"] || !comp.state["current"]["modified"] ||
confirm("You have unsaved changes. Close anyway?") confirm("You have unsaved changes. Close anyway?"),
); );
}, },
}, },
getIcon("edit") getIcon("edit"),
); );
const del = m( const del = m(
@@ -200,7 +200,7 @@ function renderTable(confsResponse, searchString): Children {
}); });
}, },
}, },
getIcon("remove") getIcon("remove"),
); );
rows.push( rows.push(
@@ -210,9 +210,9 @@ function renderTable(confsResponse, searchString): Children {
m("td.left", m("long-text", { text: conf._id })), m("td.left", m("long-text", { text: conf._id })),
m( m(
"td.right", "td.right",
m("span", [m("long-text", { text: `${conf.value}` }), edit, del]) m("span", [m("long-text", { text: `${conf.value}` }), edit, del]),
) ),
) ),
); );
} }
@@ -276,19 +276,19 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
formData formData,
) ),
); );
cb = () => comp; cb = () => comp;
overlay.open( overlay.open(
cb, cb,
() => () =>
!comp.state["current"]["modified"] || !comp.state["current"]["modified"] ||
confirm("You have unsaved changes. Close anyway?") confirm("You have unsaved changes. Close anyway?"),
); );
}, },
}, },
"New config" "New config",
); );
const subsData = [ const subsData = [
@@ -332,8 +332,8 @@ export const component: ClosureComponent = (): Component => {
"success", "success",
`${sub.name.replace( `${sub.name.replace(
/^[a-z]/, /^[a-z]/,
sub.name[0].toUpperCase() sub.name[0].toUpperCase(),
)} config updated` )} config updated`,
); );
overlay.close(cb); overlay.close(cb);
} }
@@ -345,20 +345,20 @@ export const component: ClosureComponent = (): Component => {
overlay.close(cb); overlay.close(cb);
}, },
}, },
attrs attrs,
) ),
); );
cb = () => comp; cb = () => comp;
overlay.open( overlay.open(
cb, cb,
() => () =>
!comp.state["modified"] || !comp.state["modified"] ||
confirm("You have unsaved changes. Close anyway?") confirm("You have unsaved changes. Close anyway?"),
); );
}, },
}, },
`Edit ${sub.name}` `Edit ${sub.name}`,
) ),
); );
} }
} }
@@ -374,10 +374,10 @@ export const component: ClosureComponent = (): Component => {
m( m(
".parameter-list", ".parameter-list",
{ style: "height: 400px" }, { style: "height: 400px" },
renderTable(confs, vnode.state["searchString"]) renderTable(confs, vnode.state["searchString"]),
), ),
m(".actions-bar", [newConfig].concat(subs)) m(".actions-bar", [newConfig].concat(subs)),
) ),
), ),
]; ];
}, },
+18 -6
View File
@@ -54,7 +54,9 @@ textarea {
a { a {
color: var(--color4); color: var(--color4);
cursor: pointer; cursor: pointer;
transition: background-color var(--fade), color var(--fade); transition:
background-color var(--fade),
color var(--fade);
&:visited { &:visited {
color: var(--color4); color: var(--color4);
} }
@@ -122,7 +124,9 @@ body {
margin-top: -1px; margin-top: -1px;
box-shadow: 0 0 10px #ccc; box-shadow: 0 0 10px #ccc;
overflow: hidden; overflow: hidden;
transition: height var(--fade), opacity var(--fade); transition:
height var(--fade),
opacity var(--fade);
& > div { & > div {
padding: 0.3em; padding: 0.3em;
@@ -228,7 +232,9 @@ body {
border: 1px solid var(--color1); border: 1px solid var(--color1);
border-radius: 6px; border-radius: 6px;
box-shadow: 0 0 10px #ccc; box-shadow: 0 0 10px #ccc;
transition: opacity var(--fade), top var(--fade); transition:
opacity var(--fade),
top var(--fade);
&.error { &.error {
background-color: hsl(var(--status-red) / 0.9); background-color: hsl(var(--status-red) / 0.9);
@@ -437,7 +443,9 @@ button {
border: 0; border: 0;
color: var(--color4); color: var(--color4);
border-radius: 4px; border-radius: 4px;
transition: background-color var(--fade), color var(--fade); transition:
background-color var(--fade),
color var(--fade);
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@@ -468,7 +476,9 @@ input {
border: 1px solid var(--color1); border: 1px solid var(--color1);
background-color: var(--color3); background-color: var(--color3);
border-radius: 4px; border-radius: 4px;
transition: border-color var(--fade), background-color var(--fade); transition:
border-color var(--fade),
background-color var(--fade);
&.error { &.error {
background-color: #efcbcf; background-color: #efcbcf;
border-color: #e6b2b7; border-color: #e6b2b7;
@@ -670,7 +680,9 @@ span.tag {
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
z-index: 100; z-index: 100;
transition: opacity var(--fade), top var(--fade); transition:
opacity var(--fade),
top var(--fade);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
+1 -1
View File
@@ -38,7 +38,7 @@ export function getDatalistId(options: string[]): string {
const n = m( const n = m(
"datalist", "datalist",
{ id }, { id },
options.map((o) => m("option", { value: o })) options.map((o) => m("option", { value: o })),
); );
elements.set(id, n); elements.set(id, n);
} }
+4 -4
View File
@@ -23,11 +23,11 @@ import config from "./config";
import * as store from "./store"; import * as store from "./store";
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<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"),
); );
} }
@@ -49,7 +49,7 @@ export const component: ClosureComponent = (): Component => {
return m( return m(
"loading", "loading",
{ queries: [dev] }, { queries: [dev] },
m("div", { style: "height: 100px;" }) m("div", { style: "height: 100px;" }),
); );
} }
@@ -58,7 +58,7 @@ 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 }, c["type"], c),
); );
} }
+18 -18
View File
@@ -61,7 +61,7 @@ const unpackSmartQuery = memoize((query) => {
}); });
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<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))
@@ -97,8 +97,8 @@ function renderActions(selected: Set<string>): Children {
queueTask(...tasks); queueTask(...tasks);
}, },
}, },
"Reboot" "Reboot",
) ),
); );
buttons.push( buttons.push(
@@ -115,8 +115,8 @@ function renderActions(selected: Set<string>): Children {
queueTask(...tasks); queueTask(...tasks);
}, },
}, },
"Reset" "Reset",
) ),
); );
buttons.push( buttons.push(
@@ -132,8 +132,8 @@ function renderActions(selected: Set<string>): Children {
}); });
}, },
}, },
"Push file" "Push file",
) ),
); );
buttons.push( buttons.push(
@@ -163,8 +163,8 @@ function renderActions(selected: Set<string>): Children {
if (--counter === 0) store.setTimestamp(Date.now()); if (--counter === 0) store.setTimestamp(Date.now());
}, },
}, },
"Delete" "Delete",
) ),
); );
buttons.push( buttons.push(
@@ -195,8 +195,8 @@ function renderActions(selected: Set<string>): Children {
if (--counter === 0) store.setTimestamp(Date.now()); if (--counter === 0) store.setTimestamp(Date.now());
}, },
}, },
"Tag" "Tag",
) ),
); );
buttons.push( buttons.push(
@@ -208,7 +208,7 @@ function renderActions(selected: Set<string>): Children {
onclick: () => { onclick: () => {
const ids = Array.from(selected); const ids = Array.from(selected);
const tag = prompt( const tag = prompt(
`Enter tag to unassign from ${ids.length} devices:` `Enter tag to unassign from ${ids.length} devices:`,
); );
if (!tag) return; if (!tag) return;
@@ -229,8 +229,8 @@ function renderActions(selected: Set<string>): Children {
if (--counter === 0) store.setTimestamp(Date.now()); if (--counter === 0) store.setTimestamp(Date.now());
}, },
}, },
"Untag" "Untag",
) ),
); );
return buttons; return buttons;
@@ -270,7 +270,7 @@ export const component: ClosureComponent = (): Component => {
const _sort = {}; const _sort = {};
for (const index of sortedAttrs) { for (const index of sortedAttrs) {
const param = memoizedGetSortable( const param = memoizedGetSortable(
attributes[Math.abs(index) - 1].parameter attributes[Math.abs(index) - 1].parameter,
); );
_sort[param] = Math.sign(index); _sort[param] = Math.sign(index);
} }
@@ -296,7 +296,7 @@ export const component: ClosureComponent = (): Component => {
return m.context( return m.context(
{ device: device, parameter: attr.parameter }, { device: device, parameter: attr.parameter },
attr.type || "parameter", attr.type || "parameter",
attr attr,
); );
}; };
@@ -314,10 +314,10 @@ export const component: ClosureComponent = (): Component => {
"a", "a",
{ {
href: `#!/devices/${encodeURIComponent( href: `#!/devices/${encodeURIComponent(
device["DeviceID.ID"].value[0] device["DeviceID.ID"].value[0],
)}`, )}`,
}, },
"Show" "Show",
); );
}; };
+51 -51
View File
@@ -73,7 +73,7 @@ function renderStagingSpv(task: StageTask, queueFunc, cancelFunc): Children {
[ [
m("option", { value: "true" }, "true"), m("option", { value: "true" }, "true"),
m("option", { value: "false" }, "false"), m("option", { value: "false" }, "false"),
] ],
); );
} else { } else {
const type = task.parameterValues[0][2]; const type = task.parameterValues[0][2];
@@ -134,8 +134,8 @@ function renderStagingDownload(task: StageTask): Children {
m( m(
"option", "option",
{ disabled: !t, value: t, selected: (task.fileType || "") === t }, { disabled: !t, value: t, selected: (task.fileType || "") === t },
t t,
) ),
); );
const filesList = [""] const filesList = [""]
@@ -145,16 +145,16 @@ function renderStagingDownload(task: StageTask): Children {
(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 },
f f,
) ),
); );
return [ return [
@@ -172,7 +172,7 @@ function renderStagingDownload(task: StageTask): Children {
disabled: files.fulfilling, disabled: files.fulfilling,
style: "width: 350px", style: "width: 350px",
}, },
filesList filesList,
), ),
" as ", " as ",
m( m(
@@ -182,7 +182,7 @@ function renderStagingDownload(task: StageTask): Children {
task.fileType = e.target.value; task.fileType = e.target.value;
}, },
}, },
typesList typesList,
), ),
]; ];
} }
@@ -211,12 +211,12 @@ function renderStaging(staging: Set<StageTask>): Child[] {
const queue = m( const queue = m(
"button.primary", "button.primary",
{ title: "Queue task", onclick: queueFunc, disabled: invalid.has(s) }, { title: "Queue task", onclick: queueFunc, disabled: invalid.has(s) },
"Queue" "Queue",
); );
const cancel = m( const cancel = m(
"button", "button",
{ title: "Cancel edit", onclick: cancelFunc }, { title: "Cancel edit", onclick: cancelFunc },
"Cancel" "Cancel",
); );
elements.push(m(".staging", elms, m("div.actions", queue, cancel))); elements.push(m(".staging", elms, m("div.actions", queue, cancel)));
@@ -247,8 +247,8 @@ function renderQueue(queue: Set<QueueTask>): Child[] {
queueTask(t); queueTask(t);
}, },
}, },
getIcon("retry") getIcon("retry"),
) ),
); );
} }
@@ -261,8 +261,8 @@ function renderQueue(queue: Set<QueueTask>): Child[] {
deleteTask(t); deleteTask(t);
}, },
}, },
getIcon("remove") getIcon("remove"),
) ),
); );
if (t.name === "setParameterValues") { if (t.name === "setParameterValues") {
@@ -275,56 +275,56 @@ function renderQueue(queue: Set<QueueTask>): Child[] {
mparam(t.parameterValues[0][0]), mparam(t.parameterValues[0][0]),
" to '", " to '",
mval(t.parameterValues[0][1]), mval(t.parameterValues[0][1]),
"'" "'",
), ),
m(".actions", actions) m(".actions", actions),
) ),
); );
} else if (t.name === "refreshObject") { } else if (t.name === "refreshObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
m("span", "Refresh ", mparam(t.parameterName)), m("span", "Refresh ", mparam(t.parameterName)),
m(".actions", actions) m(".actions", actions),
) ),
); );
} else if (t.name === "reboot") { } else if (t.name === "reboot") {
details.push(m(`div.${t.status}`, "Reboot", m(".actions", actions))); details.push(m(`div.${t.status}`, "Reboot", m(".actions", actions)));
} else if (t.name === "factoryReset") { } else if (t.name === "factoryReset") {
details.push( details.push(
m(`div.${t.status}`, "Factory reset", m(".actions", actions)) m(`div.${t.status}`, "Factory reset", m(".actions", actions)),
); );
} else if (t.name === "addObject") { } else if (t.name === "addObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
m("span", "Add ", mparam(t.objectName)), m("span", "Add ", mparam(t.objectName)),
m(".actions", actions) m(".actions", actions),
) ),
); );
} else if (t.name === "deleteObject") { } else if (t.name === "deleteObject") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
m("span", "Delete ", mparam(t.objectName)), m("span", "Delete ", mparam(t.objectName)),
m(".actions", actions) m(".actions", actions),
) ),
); );
} else if (t.name === "getParameterValues") { } else if (t.name === "getParameterValues") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
`Refresh ${t.parameterNames.length} parameters`, `Refresh ${t.parameterNames.length} parameters`,
m(".actions", actions) m(".actions", actions),
) ),
); );
} else if (t.name === "download") { } else if (t.name === "download") {
details.push( details.push(
m( m(
`div.${t.status}`, `div.${t.status}`,
`Push file: ${t.fileName} (${t.fileType})`, `Push file: ${t.fileName} (${t.fileType})`,
m(".actions", actions) m(".actions", actions),
) ),
); );
} else { } else {
details.push(m(`div.${t.status}`, t.name, m(".actions", actions))); details.push(m(`div.${t.status}`, t.name, m(".actions", actions)));
@@ -342,7 +342,7 @@ function renderNotifications(notifs): Child[] {
let buttons; let buttons;
if (n.actions) { if (n.actions) {
const btns = Object.entries(n.actions).map(([label, onclick]) => const btns = Object.entries(n.actions).map(([label, onclick]) =>
m("button.primary", { onclick: onclick }, label) m("button.primary", { onclick: onclick }, label),
); );
if (btns.length) buttons = m("div", { style: "float: right" }, btns); if (btns.length) buttons = m("div", { style: "float: right" }, btns);
} }
@@ -366,8 +366,8 @@ function renderNotifications(notifs): Child[] {
}, },
key: n.timestamp, key: n.timestamp,
}, },
m("div", buttons, n.message) m("div", buttons, n.message),
) ),
); );
} }
return notificationElements; return notificationElements;
@@ -401,7 +401,7 @@ const component: ClosureComponent = (): Component => {
height = Math.max( height = Math.max(
height, height,
(s.dom as HTMLDivElement).offsetTop + (s.dom as HTMLDivElement).offsetTop +
(s.dom as HTMLDivElement).offsetHeight (s.dom as HTMLDivElement).offsetHeight,
); );
} }
} else if (vnode.state["mouseIn"]) { } else if (vnode.state["mouseIn"]) {
@@ -424,7 +424,7 @@ const component: ClosureComponent = (): Component => {
disabled: !statusCount.queued, disabled: !statusCount.queued,
onclick: () => { onclick: () => {
const tasks = Array.from(getQueue()).filter( const tasks = Array.from(getQueue()).filter(
(t) => t["status"] === "queued" (t) => t["status"] === "queued",
); );
commit( commit(
tasks, tasks,
@@ -432,7 +432,7 @@ const component: ClosureComponent = (): Component => {
if (err) { if (err) {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${err.message}` `${deviceId}: ${err.message}`,
); );
return; return;
} }
@@ -440,7 +440,7 @@ const component: ClosureComponent = (): Component => {
if (connectionRequestStatus !== "OK") { if (connectionRequestStatus !== "OK") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: ${connectionRequestStatus}` `${deviceId}: ${connectionRequestStatus}`,
); );
return; return;
} }
@@ -449,13 +449,13 @@ const component: ClosureComponent = (): Component => {
if (t.status === "stale") { if (t.status === "stale") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: No contact from device` `${deviceId}: No contact from device`,
); );
return; return;
} else if (t.status === "fault") { } else if (t.status === "fault") {
notifications.push( notifications.push(
"error", "error",
`${deviceId}: Task(s) faulted` `${deviceId}: Task(s) faulted`,
); );
return; return;
} }
@@ -463,9 +463,9 @@ const component: ClosureComponent = (): Component => {
notifications.push( notifications.push(
"success", "success",
`${deviceId}: Task(s) committed` `${deviceId}: Task(s) committed`,
); );
} },
) )
.then(() => { .then(() => {
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
@@ -475,7 +475,7 @@ const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Commit" "Commit",
), ),
m( m(
"button", "button",
@@ -484,8 +484,8 @@ const component: ClosureComponent = (): Component => {
onclick: clear, onclick: clear,
disabled: !queueElements.length, disabled: !queueElements.length,
}, },
"Clear" "Clear",
) ),
); );
statusElement = m( statusElement = m(
@@ -493,24 +493,24 @@ const component: ClosureComponent = (): Component => {
m( m(
"span.queued", "span.queued",
{ class: statusCount.queued ? "active" : "" }, { class: statusCount.queued ? "active" : "" },
`Queued: ${statusCount.queued}` `Queued: ${statusCount.queued}`,
), ),
m( m(
"span.pending", "span.pending",
{ class: statusCount.pending ? "active" : "" }, { class: statusCount.pending ? "active" : "" },
`Pending: ${statusCount.pending}` `Pending: ${statusCount.pending}`,
), ),
m( m(
"span.fault", "span.fault",
{ class: statusCount.fault ? "active" : "" }, { class: statusCount.fault ? "active" : "" },
`Fault: ${statusCount.fault}` `Fault: ${statusCount.fault}`,
), ),
m( m(
"span.stale", "span.stale",
{ class: statusCount.stale ? "active" : "" }, { class: statusCount.stale ? "active" : "" },
`Stale: ${statusCount.stale}` `Stale: ${statusCount.stale}`,
), ),
actions actions,
); );
drawerElement = m( drawerElement = m(
@@ -545,7 +545,7 @@ const component: ClosureComponent = (): Component => {
}, },
}, },
statusElement, statusElement,
stagingElements.length ? stagingElements : m(".queue", queueElements) stagingElements.length ? stagingElements : m(".queue", queueElements),
); );
} }
@@ -560,8 +560,8 @@ const component: ClosureComponent = (): Component => {
onupdate: repositionNotifications, onupdate: repositionNotifications,
oncreate: repositionNotifications, oncreate: repositionNotifications,
}, },
notificationElements notificationElements,
) ),
); );
}, },
}; };
+2 -2
View File
@@ -33,7 +33,7 @@ function onError(): void {
Reload: () => { Reload: () => {
window.location.reload(); window.location.reload();
}, },
} },
); );
} }
} }
@@ -65,7 +65,7 @@ export function loadYaml(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
import("yaml") import("yaml")
.then((module) => { .then((module) => {
yaml = module; yaml = module.default;
resolve(); resolve();
}) })
.catch((err) => { .catch((err) => {
+8 -8
View File
@@ -66,11 +66,11 @@ const unpackSmartQuery = memoize((query) => {
}); });
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<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"),
); );
} }
@@ -131,7 +131,7 @@ export const component: ClosureComponent = (): Component => {
const valueCallback = (attr, fault): Children => { const valueCallback = (attr, fault): Children => {
if (attr.id === "device") { if (attr.id === "device") {
const deviceHref = `#!/devices/${encodeURIComponent( const deviceHref = `#!/devices/${encodeURIComponent(
fault["device"] fault["device"],
)}`; )}`;
return m("a", { href: deviceHref }, fault["device"]); return m("a", { href: deviceHref }, fault["device"]);
@@ -175,13 +175,13 @@ export const component: ClosureComponent = (): Component => {
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.setTimestamp(Date.now()); store.setTimestamp(Date.now());
}) })
@@ -191,7 +191,7 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Delete" "Delete",
); );
}; };
} }
@@ -208,7 +208,7 @@ export const component: ClosureComponent = (): Component => {
m( m(
"loading", "loading",
{ queries: [faults, count] }, { queries: [faults, count] },
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
), ),
]; ];
}, },
+14 -14
View File
@@ -77,13 +77,13 @@ function upload(
file: File, file: File,
headers: Record<string, string>, headers: Record<string, string>,
abortSignal?: AbortSignal, abortSignal?: AbortSignal,
progressListener?: (e: ProgressEvent) => void progressListener?: (e: ProgressEvent) => void,
): Promise<void> { ): Promise<void> {
headers = Object.assign( headers = Object.assign(
{ {
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
}, },
headers headers,
); );
return store.xhrRequest({ return store.xhrRequest({
method: "PUT", method: "PUT",
@@ -112,11 +112,11 @@ const getDownloadUrl = memoize((filter) => {
}); });
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<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"),
); );
} }
@@ -236,7 +236,7 @@ export const component: ClosureComponent = (): Component => {
file, file,
headers, headers,
abortController.signal, abortController.signal,
progressListener progressListener,
); );
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
notifications.push("success", "File created"); notifications.push("success", "File created");
@@ -247,8 +247,8 @@ export const component: ClosureComponent = (): Component => {
progress = -1; progress = -1;
}, },
}, },
formData formData,
) ),
); );
cb = () => { cb = () => {
if (progress < 0) return [null, comp]; if (progress < 0) return [null, comp];
@@ -257,7 +257,7 @@ export const component: ClosureComponent = (): Component => {
"div.progress", "div.progress",
m("div.progress-bar", { m("div.progress-bar", {
style: `width: ${Math.trunc(progress * 100)}%`, style: `width: ${Math.trunc(progress * 100)}%`,
}) }),
), ),
comp, comp,
]; ];
@@ -273,7 +273,7 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"New" "New",
), ),
m( m(
"button.primary", "button.primary",
@@ -290,13 +290,13 @@ export const component: ClosureComponent = (): Component => {
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.setTimestamp(Date.now()); store.setTimestamp(Date.now());
}) })
@@ -306,7 +306,7 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Delete" "Delete",
), ),
]; ];
}; };
@@ -324,7 +324,7 @@ export const component: ClosureComponent = (): Component => {
m( m(
"loading", "loading",
{ queries: [files, count] }, { queries: [files, count] },
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
), ),
]; ];
}, },
+1 -1
View File
@@ -35,7 +35,7 @@ const getAutocomplete = memoize((resource) => {
.map((s) => ({ .map((s) => ({
value: `${s}: `, value: `${s}: `,
tip: smartQuery.getTip(resource, s), tip: smartQuery.getTip(resource, s),
})) })),
); );
}); });
return autocomplete; return autocomplete;
+1 -1
View File
@@ -25,6 +25,6 @@ export function getIcon(name: string): Children {
return m( return m(
`svg.icon.icon-${name}`, `svg.icon.icon-${name}`,
{ key: `icon-${name}` }, { key: `icon-${name}` },
m("use", { href: `${ICONS_SVG}#icon-${name}` }) m("use", { href: `${ICONS_SVG}#icon-${name}` }),
); );
} }
+13 -11
View File
@@ -41,7 +41,9 @@ function renderTable(
downloadUrl?: string, downloadUrl?: string,
valueCallback?: (attr: Attribute, record: Record<string, any>) => Children, valueCallback?: (attr: Attribute, record: Record<string, any>) => Children,
actionsCallback?: Children | ((sel: Set<string>) => Children), actionsCallback?: Children | ((sel: Set<string>) => Children),
recordActionsCallback?: Children | ((record: Record<string, any>) => Children) recordActionsCallback?:
| Children
| ((record: Record<string, any>) => Children),
): Children { ): Children {
const records = data || []; const records = data || [];
@@ -92,7 +94,7 @@ function renderTable(
onSort(i); onSort(i);
}, },
}, },
symbol symbol,
); );
labels.push(m("th", [label, sortable])); labels.push(m("th", [label, sortable]));
@@ -162,8 +164,8 @@ function renderTable(
if (!selected.delete(id)) selected.add(id); if (!selected.delete(id)) selected.add(id);
}, },
}, },
tds tds,
) ),
); );
} }
@@ -184,19 +186,19 @@ function renderTable(
disabled: disabled:
!data.length || records.length >= Math.min(MAX_PAGE_SIZE, total), !data.length || records.length >= Math.min(MAX_PAGE_SIZE, total),
}, },
"More" "More",
) ),
); );
if (downloadUrl) { if (downloadUrl) {
footerElements.push( footerElements.push(
m("a.download-csv", { href: downloadUrl, download: "" }, "Download") m("a.download-csv", { href: downloadUrl, download: "" }, "Download"),
); );
} }
const tfoot = m( const tfoot = m(
"tfoot", "tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements)) m("tr", m("td", { colspan: labels.length }, footerElements)),
); );
const children = [ const children = [
@@ -204,7 +206,7 @@ function renderTable(
"table.table.highlight", "table.table.highlight",
m("thead", m("tr", labels)), m("thead", m("tr", labels)),
m("tbody", rows), m("tbody", rows),
tfoot tfoot,
), ),
]; ];
@@ -241,7 +243,7 @@ const component: ClosureComponent = (): Component => {
const sortArray = new Set( const sortArray = new Set(
Object.keys(sortAttributes) Object.keys(sortAttributes)
.map((x) => (parseInt(x) + 1) * sortAttributes[x]) .map((x) => (parseInt(x) + 1) * sortAttributes[x])
.filter((x) => x) .filter((x) => x),
); );
for (const num of events) { for (const num of events) {
if (sortArray.delete(num + 1)) sortArray.add(-(num + 1)); if (sortArray.delete(num + 1)) sortArray.add(-(num + 1));
@@ -263,7 +265,7 @@ const component: ClosureComponent = (): Component => {
downloadUrl, downloadUrl,
valueCallback, valueCallback,
actionsCallback, actionsCallback,
recordActionsCallback recordActionsCallback,
); );
}, },
}; };
+2 -2
View File
@@ -57,7 +57,7 @@ const component: ClosureComponent = (): Component => {
m( m(
"div.logo", "div.logo",
m("img", { src: LOGO_SVG }), m("img", { src: LOGO_SVG }),
m("span.version", "v" + VERSION) m("span.version", "v" + VERSION),
), ),
m(userMenu), m(userMenu),
m(menu, attrs), m(menu, attrs),
@@ -68,7 +68,7 @@ const component: ClosureComponent = (): Component => {
sideMenu, sideMenu,
m("#content", { class: `page-${vnode.attrs["page"]}` }, [ m("#content", { class: `page-${vnode.attrs["page"]}` }, [
vnode.children, vnode.children,
]) ]),
), ),
overlay.render(), overlay.render(),
m(datalist), m(datalist),
+7 -7
View File
@@ -25,7 +25,7 @@ import * as overlay from "./overlay";
import changePasswordComponent from "./change-password-component"; import changePasswordComponent from "./change-password-component";
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
return Promise.resolve(args); return Promise.resolve(args);
} }
@@ -53,7 +53,7 @@ export const component: ClosureComponent = (): Component => {
oninput: (e) => { oninput: (e) => {
vnode.state["username"] = e.target.value; vnode.state["username"] = e.target.value;
}, },
}) }),
), ),
m( m(
"p", "p",
@@ -65,7 +65,7 @@ export const component: ClosureComponent = (): Component => {
oninput: (e) => { oninput: (e) => {
vnode.state["password"] = e.target.value; vnode.state["password"] = e.target.value;
}, },
}) }),
), ),
m( m(
"p", "p",
@@ -87,9 +87,9 @@ export const component: ClosureComponent = (): Component => {
return false; return false;
}, },
}, },
"Login" "Login",
) ),
) ),
), ),
m( m(
"a", "a",
@@ -107,7 +107,7 @@ export const component: ClosureComponent = (): Component => {
overlay.open(cb); overlay.open(cb);
}, },
}, },
"Change password" "Change password",
), ),
]; ];
}, },
+2 -2
View File
@@ -57,7 +57,7 @@ const component: ClosureComponent = (): Component => {
onupdate: (vnode2) => { onupdate: (vnode2) => {
if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) { if (vnode2.dom.clientWidth === vnode2.dom.scrollWidth) {
(vnode2.dom as HTMLElement).classList.remove( (vnode2.dom as HTMLElement).classList.remove(
"long-text-overflowed" "long-text-overflowed",
); );
(vnode2.dom as HTMLElement).onclick = null; (vnode2.dom as HTMLElement).onclick = null;
} else { } else {
@@ -67,7 +67,7 @@ const component: ClosureComponent = (): Component => {
class: "long-text", class: "long-text",
title: text, title: text,
}, },
text text,
); );
}, },
}; };
+8 -8
View File
@@ -30,8 +30,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["overview"] }, { class: active["overview"] },
m("a", { href: "#!/overview" }, "Overview") m("a", { href: "#!/overview" }, "Overview"),
) ),
); );
} }
@@ -40,8 +40,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["devices"] }, { class: active["devices"] },
m("a", { href: "#!/devices" }, "Devices") m("a", { href: "#!/devices" }, "Devices"),
) ),
); );
} }
@@ -50,8 +50,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["faults"] }, { class: active["faults"] },
m("a", { href: "#!/faults" }, "Faults") m("a", { href: "#!/faults" }, "Faults"),
) ),
); );
} }
@@ -67,8 +67,8 @@ const component: ClosureComponent = (): Component => {
m( m(
"li", "li",
{ class: active["admin"] }, { class: active["admin"] },
m("a", { href: "#!/admin" }, "Admin") m("a", { href: "#!/admin" }, "Admin"),
) ),
); );
break; break;
} }
+1 -1
View File
@@ -31,7 +31,7 @@ const notifications = new Set<Notification>();
export function push( export function push(
type: string, type: string,
message: string, message: string,
actions?: { [label: string]: () => void } actions?: { [label: string]: () => void },
): Notification { ): Notification {
const n: Notification = { const n: Notification = {
type: type, type: type,
+3 -3
View File
@@ -27,7 +27,7 @@ let closeCallback: CloseCallback = null;
export function open( export function open(
callback: OverlayCallback, callback: OverlayCallback,
closeCb: CloseCallback = null closeCb: CloseCallback = null,
): void { ): void {
overlayCallback = callback; overlayCallback = callback;
closeCallback = closeCb; closeCallback = closeCb;
@@ -74,8 +74,8 @@ export function render(): Children {
e.stopPropagation(); e.stopPropagation();
}, },
}, },
overlayCallback() overlayCallback(),
) ),
); );
} }
+1 -1
View File
@@ -47,7 +47,7 @@ function queryCharts(charts: Record<string, unknown>): Record<string, unknown> {
export function init(): Promise<Record<string, unknown>> { export function init(): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 1)) { if (!window.authorizer.hasAccess("devices", 1)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page"),
); );
} }
+20 -20
View File
@@ -129,7 +129,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
.then(() => { .then(() => {
notifications.push( notifications.push(
"success", "success",
`Permission ${exists ? "updated" : "created"}` `Permission ${exists ? "updated" : "created"}`,
); );
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
resolve(null); resolve(null);
@@ -170,11 +170,11 @@ const getDownloadUrl = memoize((filter) => {
}); });
export function init( export function init(
args: Record<string, unknown> args: Record<string, unknown>,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("permissions", 2)) { if (!window.authorizer.hasAccess("permissions", 2)) {
return Promise.reject( return Promise.reject(
new Error("You are not authorized to view this page") new Error("You are not authorized to view this page"),
); );
} }
const sort = args.hasOwnProperty("sort") ? "" + args["sort"] : ""; const sort = args.hasOwnProperty("sort") ? "" + args["sort"] : "";
@@ -274,13 +274,13 @@ export const component: ClosureComponent = (): Component => {
base: permission, base: permission,
oncreate: (_vnode) => { oncreate: (_vnode) => {
_vnode.dom.querySelector( _vnode.dom.querySelector(
"input[name='role']" "input[name='role']",
).disabled = true; ).disabled = true;
_vnode.dom.querySelector( _vnode.dom.querySelector(
"select[name='access']" "select[name='access']",
).disabled = true; ).disabled = true;
_vnode.dom.querySelector( _vnode.dom.querySelector(
"select[name='resource']" "select[name='resource']",
).disabled = true; ).disabled = true;
}, },
actionHandler: (action, object) => { actionHandler: (action, object) => {
@@ -305,19 +305,19 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
formData formData,
) ),
); );
cb = () => comp; cb = () => comp;
overlay.open( overlay.open(
cb, cb,
() => () =>
!comp.state["current"]["modified"] || !comp.state["current"]["modified"] ||
confirm("You have unsaved changes. Close anyway?") confirm("You have unsaved changes. Close anyway?"),
); );
}, },
}, },
"Show" "Show",
), ),
]; ];
}; };
@@ -356,19 +356,19 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
formData formData,
) ),
); );
cb = () => comp; cb = () => comp;
overlay.open( overlay.open(
cb, cb,
() => () =>
!comp.state["current"]["modified"] || !comp.state["current"]["modified"] ||
confirm("You have unsaved changes. Close anyway?") confirm("You have unsaved changes. Close anyway?"),
); );
}, },
}, },
"New" "New",
), ),
m( m(
"button.primary", "button.primary",
@@ -378,7 +378,7 @@ export const component: ClosureComponent = (): Component => {
onclick: (e) => { onclick: (e) => {
if ( if (
!confirm( !confirm(
`Deleting ${selected.size} permissions. Are you sure?` `Deleting ${selected.size} permissions. Are you sure?`,
) )
) )
return; return;
@@ -387,13 +387,13 @@ export const component: ClosureComponent = (): Component => {
e.target.disabled = true; e.target.disabled = true;
Promise.all( Promise.all(
Array.from(selected).map((id) => Array.from(selected).map((id) =>
store.deleteResource("permissions", id) store.deleteResource("permissions", id),
) ),
) )
.then((res) => { .then((res) => {
notifications.push( notifications.push(
"success", "success",
`${res.length} permissions deleted` `${res.length} permissions deleted`,
); );
store.setTimestamp(Date.now()); store.setTimestamp(Date.now());
}) })
@@ -403,7 +403,7 @@ export const component: ClosureComponent = (): Component => {
}); });
}, },
}, },
"Delete" "Delete",
), ),
]; ];
}; };
@@ -421,7 +421,7 @@ export const component: ClosureComponent = (): Component => {
m( m(
"loading", "loading",
{ queries: [permissions, count] }, { queries: [permissions, count] },
m(indexTableComponent, attrs) m(indexTableComponent, attrs),
), ),
]; ];
}, },
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more