Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa95d7a310 | ||
|
|
d299ef3a59 | ||
|
|
2c606b268e | ||
|
|
1b86f41ad7 | ||
|
|
c4754a9b55 | ||
|
|
f24111ea9f | ||
|
|
8f662fb152 | ||
|
|
dd722ba6e0 | ||
|
|
88a84788a5 | ||
|
|
73baffc3b9 | ||
|
|
0188ac860b | ||
|
|
7f295beeec | ||
|
|
2ac536bf8f | ||
|
|
f63e357b29 |
No files matched your search
@@ -1,5 +1,22 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 1.2.8 (2021-10-27)
|
||||||
|
|
||||||
|
- Fix a remote code execution security vulnerability in genieacs-ui.
|
||||||
|
- All UI components can now be configured using fully dynamic expressions.
|
||||||
|
Previously some component types only accept fixed string values as properties.
|
||||||
|
- The `container` UI component can now be configured with an `element` property
|
||||||
|
that's either a string or a nested pair of `tag` and `attributes` properties.
|
||||||
|
The various attributes under the `attributes` property can now make use of a
|
||||||
|
new function `ENCODEURICOMPONENT()` to facilitate creating custom hyperlinks
|
||||||
|
in the UI.
|
||||||
|
- Improve sorting buttons' behavior in the device listing page and other pages.
|
||||||
|
Sorting by multiple columns should now feel more intuitive.
|
||||||
|
- Support the modulo (%) operator in expressions.
|
||||||
|
- Fix a regression in the previous release where the config option 'ui.pageSize'
|
||||||
|
no longer works.
|
||||||
|
- Fix process crash when a CPE sends an unsupported ACS method.
|
||||||
|
|
||||||
## 1.2.7 (2021-09-18)
|
## 1.2.7 (2021-09-18)
|
||||||
|
|
||||||
- Fix regression causing frequent invalid session errors.
|
- Fix regression causing frequent invalid session errors.
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ function rmDirSync(dirPath): void {
|
|||||||
fs.rmdirSync(dirPath);
|
fs.rmdirSync(dirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For lockfileVersion = 1
|
||||||
function stripDevDeps(deps): void {
|
function stripDevDeps(deps): void {
|
||||||
if (!deps["dependencies"]) return;
|
if (!deps["dependencies"]) return;
|
||||||
for (const [k, v] of Object.entries(deps["dependencies"])) {
|
for (const [k, v] of Object.entries(deps["dependencies"])) {
|
||||||
@@ -83,6 +84,15 @@ function stripDevDeps(deps): void {
|
|||||||
if (!Object.keys(deps["dependencies"]).length) delete deps["dependencies"];
|
if (!Object.keys(deps["dependencies"]).length) delete deps["dependencies"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For lockfileVersion = 2
|
||||||
|
function stripDevDeps2(deps): void {
|
||||||
|
if (!deps["packages"]) return;
|
||||||
|
for (const [k, v] of Object.entries(deps["packages"])) {
|
||||||
|
delete v["devDependencies"];
|
||||||
|
if (v["dev"]) delete deps["packages"][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function xmlTostring(xml): string {
|
function xmlTostring(xml): string {
|
||||||
const children = [];
|
const children = [];
|
||||||
for (const c of xml.children || []) children.push(xmlTostring(c));
|
for (const c of xml.children || []) children.push(xmlTostring(c));
|
||||||
@@ -140,6 +150,7 @@ async function init(): Promise<string[]> {
|
|||||||
);
|
);
|
||||||
npmShrinkwrapJson["version"] = packageJson["version"];
|
npmShrinkwrapJson["version"] = packageJson["version"];
|
||||||
stripDevDeps(npmShrinkwrapJson);
|
stripDevDeps(npmShrinkwrapJson);
|
||||||
|
stripDevDeps2(npmShrinkwrapJson);
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(OUTPUT_DIR, "npm-shrinkwrap.json"),
|
path.resolve(OUTPUT_DIR, "npm-shrinkwrap.json"),
|
||||||
JSON.stringify(npmShrinkwrapJson, null, 2)
|
JSON.stringify(npmShrinkwrapJson, null, 2)
|
||||||
|
|||||||
@@ -168,3 +168,5 @@ IPv
|
|||||||
downloadSuccessOnTimeout
|
downloadSuccessOnTimeout
|
||||||
TransferComplete
|
TransferComplete
|
||||||
nginx
|
nginx
|
||||||
|
ENCODEURICOMPONENT
|
||||||
|
pageSize
|
||||||
@@ -1110,8 +1110,8 @@ function getCanRaiseCallback(
|
|||||||
const vars = context.getIsNullRelations(i);
|
const vars = context.getIsNullRelations(i);
|
||||||
if (!context.isIsNull(i)) {
|
if (!context.isIsNull(i)) {
|
||||||
if (!(idx & 1)) return true;
|
if (!(idx & 1)) return true;
|
||||||
if (!set.has(idx ^ 3)) return true;
|
|
||||||
for (const n of vars) if (set.has((n << 2) ^ 3)) return true;
|
for (const n of vars) if (set.has((n << 2) ^ 3)) return true;
|
||||||
|
if (set.has(idx ^ 3)) return false;
|
||||||
for (const n of vars) if (!set.has((n << 2) ^ 2)) return false;
|
for (const n of vars) if (!set.has((n << 2) ^ 2)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,8 +87,6 @@ function binaryLeft(
|
|||||||
rest.reduce((acc, ch) => {
|
rest.reduce((acc, ch) => {
|
||||||
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]);
|
||||||
if (Array.isArray(another) && op === another[0])
|
|
||||||
return [op, acc].concat(another.slice(1));
|
|
||||||
return [op, acc, another];
|
return [op, acc, another];
|
||||||
}, first)
|
}, first)
|
||||||
);
|
);
|
||||||
@@ -287,7 +285,11 @@ const lang = parsimmon.createLanguage({
|
|||||||
.skip(parsimmon.optWhitespace),
|
.skip(parsimmon.optWhitespace),
|
||||||
binaryLeft(
|
binaryLeft(
|
||||||
parsimmon
|
parsimmon
|
||||||
.alt(parsimmon.string("*"), parsimmon.string("/"))
|
.alt(
|
||||||
|
parsimmon.string("*"),
|
||||||
|
parsimmon.string("/"),
|
||||||
|
parsimmon.string("%")
|
||||||
|
)
|
||||||
.skip(parsimmon.optWhitespace),
|
.skip(parsimmon.optWhitespace),
|
||||||
parsimmon.alt(
|
parsimmon.alt(
|
||||||
r.Value,
|
r.Value,
|
||||||
@@ -386,6 +388,7 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
"-": 31,
|
"-": 31,
|
||||||
"*": 32,
|
"*": 32,
|
||||||
"/": 32,
|
"/": 32,
|
||||||
|
"%": 32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const op = exp[0].toUpperCase();
|
const op = exp[0].toUpperCase();
|
||||||
|
|||||||
@@ -244,9 +244,12 @@ export function evaluateCallback(exp: Expression): Expression {
|
|||||||
} else if (exp[0] === "/") {
|
} else if (exp[0] === "/") {
|
||||||
return reduce(exp, (a, b, i) => {
|
return reduce(exp, (a, b, i) => {
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
if (!isArray(a) && !isArray(b))
|
if (isArray(a) || isArray(b)) return REDUCE_SKIP;
|
||||||
return i === 0 ? toNumber(a) / toNumber(b) : toNumber(a) * toNumber(b);
|
const n1 = toNumber(a);
|
||||||
return REDUCE_SKIP;
|
const n2 = toNumber(b);
|
||||||
|
if (i !== 0) return n1 * n2;
|
||||||
|
if (n2 === 0) return null;
|
||||||
|
return n1 / n2;
|
||||||
});
|
});
|
||||||
} else if (exp[0] === "+") {
|
} else if (exp[0] === "+") {
|
||||||
return reduce(exp, (a, b) => {
|
return reduce(exp, (a, b) => {
|
||||||
@@ -261,6 +264,15 @@ export function evaluateCallback(exp: Expression): Expression {
|
|||||||
return i === 0 ? toNumber(a) - toNumber(b) : toNumber(a) + toNumber(b);
|
return i === 0 ? toNumber(a) - toNumber(b) : toNumber(a) + toNumber(b);
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
|
} else if (exp[0] === "%") {
|
||||||
|
return reduce(exp, (a, b, i) => {
|
||||||
|
if (a == null || b == null) return null;
|
||||||
|
if (isArray(a) || isArray(b) || i !== 0) return REDUCE_SKIP;
|
||||||
|
const n1 = toNumber(a);
|
||||||
|
const n2 = Math.trunc(toNumber(b));
|
||||||
|
if (n2 === 0) return null;
|
||||||
|
return n1 % n2;
|
||||||
|
});
|
||||||
} else if (exp[0] === "||") {
|
} else if (exp[0] === "||") {
|
||||||
return reduce(exp, (a, b) => {
|
return reduce(exp, (a, b) => {
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
|
|||||||
+36
@@ -45,6 +45,7 @@ import {
|
|||||||
InformRequest,
|
InformRequest,
|
||||||
Preset,
|
Preset,
|
||||||
GetRPCMethodsResponse,
|
GetRPCMethodsResponse,
|
||||||
|
CpeFault,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { IncomingMessage, ServerResponse } from "http";
|
import { IncomingMessage, ServerResponse } from "http";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
@@ -1256,6 +1257,41 @@ async function processRequest(
|
|||||||
session.clearProvisions(sessionContext);
|
session.clearProvisions(sessionContext);
|
||||||
}
|
}
|
||||||
return nextRpc(sessionContext);
|
return nextRpc(sessionContext);
|
||||||
|
} else if (rpc.unknownMethod) {
|
||||||
|
if (sessionContext.state === 1) {
|
||||||
|
logger.accessWarn({
|
||||||
|
sessionContext: sessionContext,
|
||||||
|
message: "Method not supported",
|
||||||
|
method: rpc.unknownMethod,
|
||||||
|
});
|
||||||
|
|
||||||
|
const f: CpeFault = {
|
||||||
|
faultCode: "Server",
|
||||||
|
faultString: "CWMP fault",
|
||||||
|
detail: {
|
||||||
|
faultCode: "8000",
|
||||||
|
faultString: "Method not supported",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = soap.response({
|
||||||
|
id: rpc.id,
|
||||||
|
acsFault: f,
|
||||||
|
cwmpVersion: sessionContext.cwmpVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
return writeResponse(sessionContext, res);
|
||||||
|
} else if (sessionContext.state === 2) {
|
||||||
|
const fault = {
|
||||||
|
code: "invalid_response",
|
||||||
|
message: "Response name does not match request name",
|
||||||
|
};
|
||||||
|
recordFault(sessionContext, fault);
|
||||||
|
session.clearProvisions(sessionContext);
|
||||||
|
return nextRpc(sessionContext);
|
||||||
|
} else {
|
||||||
|
return reportBadState(sessionContext);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// CPE sent empty response
|
// CPE sent empty response
|
||||||
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ async function fetchConfig(): Promise<[Config, UiConfig]> {
|
|||||||
device: {},
|
device: {},
|
||||||
index: {},
|
index: {},
|
||||||
overview: {},
|
overview: {},
|
||||||
|
pageSize: null,
|
||||||
};
|
};
|
||||||
const _config = {};
|
const _config = {};
|
||||||
|
|
||||||
|
|||||||
+13
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { platform } from "os";
|
import { platform } from "os";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
import { domainToASCII } from "url";
|
||||||
|
|
||||||
export interface PingResult {
|
export interface PingResult {
|
||||||
packetsTransmitted: number;
|
packetsTransmitted: number;
|
||||||
@@ -30,11 +31,23 @@ export interface PingResult {
|
|||||||
mdev: number;
|
mdev: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidHost(host: string): boolean {
|
||||||
|
// Valid chars in IPv4, IPv6, domain names
|
||||||
|
if (/^[a-zA-Z0-9\-.:[\]-]+$/.test(host)) return true;
|
||||||
|
|
||||||
|
// Check if input is an IDN convert to Punycode
|
||||||
|
// Can't merge with above because domainToASCII doesn't accept IP addresses
|
||||||
|
return /^[a-zA-Z0-9\-.:[\]-]+$/.test(domainToASCII(host));
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
|
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
|
||||||
|
// Validate input to prevent possible remote code execution
|
||||||
|
// Credit to Alex Hordijk for reporting this vulnerability
|
||||||
|
if (!isValidHost(host)) return callback(new Error("Invalid host"));
|
||||||
host = host.replace("[", "").replace("]", "");
|
host = host.replace("[", "").replace("]", "");
|
||||||
switch (platform()) {
|
switch (platform()) {
|
||||||
case "linux":
|
case "linux":
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import * as later from "later";
|
import * as later from "@breejs/later";
|
||||||
|
|
||||||
function md532(str): number {
|
function md532(str): number {
|
||||||
const digest = crypto.createHash("md5").update(str).digest();
|
const digest = crypto.createHash("md5").update(str).digest();
|
||||||
|
|||||||
+18
-1
@@ -730,6 +730,18 @@ function RequestDownloadResponse(): string {
|
|||||||
return "<cwmp:RequestDownloadResponse></cwmp:RequestDownloadResponse>";
|
return "<cwmp:RequestDownloadResponse></cwmp:RequestDownloadResponse>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AcsFault(f: CpeFault): string {
|
||||||
|
return `<soap-env:Body:Fault><faultcode>${encodeEntities(
|
||||||
|
f.faultCode
|
||||||
|
)}</faultcode><faultstring>${encodeEntities(
|
||||||
|
f.faultString
|
||||||
|
)}</faultstring><detail><cwmp:Fault><FaultCode>${encodeEntities(
|
||||||
|
f.detail.faultCode
|
||||||
|
)}</FaultCode><FaultString>${encodeEntities(
|
||||||
|
f.detail.faultString
|
||||||
|
)}</FaultString></cwmp:Fault></detail></soap-env:Body:Fault>`;
|
||||||
|
}
|
||||||
|
|
||||||
function faultStruct(xml: Element): FaultStruct {
|
function faultStruct(xml: Element): FaultStruct {
|
||||||
let faultCode: string,
|
let faultCode: string,
|
||||||
faultString: string,
|
faultString: string,
|
||||||
@@ -838,6 +850,7 @@ export function request(
|
|||||||
cpeRequest: null,
|
cpeRequest: null,
|
||||||
cpeFault: null,
|
cpeFault: null,
|
||||||
cpeResponse: null,
|
cpeResponse: null,
|
||||||
|
unknownMethod: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!body.length) return rpc;
|
if (!body.length) return rpc;
|
||||||
@@ -959,7 +972,8 @@ export function request(
|
|||||||
rpc.cpeFault = fault(methodElement);
|
rpc.cpeFault = fault(methodElement);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`8000 Method not supported ${methodElement.localName}`);
|
rpc.unknownMethod = methodElement.localName;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rpc;
|
return rpc;
|
||||||
@@ -987,6 +1001,7 @@ export function response(rpc: {
|
|||||||
id: string;
|
id: string;
|
||||||
acsRequest?: AcsRequest;
|
acsRequest?: AcsRequest;
|
||||||
acsResponse?: AcsResponse;
|
acsResponse?: AcsResponse;
|
||||||
|
acsFault?: CpeFault;
|
||||||
cwmpVersion?: string;
|
cwmpVersion?: string;
|
||||||
}): { code: number; headers: Record<string, string>; data: string } {
|
}): { code: number; headers: Record<string, string>; data: string } {
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -1055,6 +1070,8 @@ export function response(rpc: {
|
|||||||
`Unknown method request ${(rpc.acsRequest as AcsRequest).name}`
|
`Unknown method request ${(rpc.acsRequest as AcsRequest).name}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (rpc.acsFault) {
|
||||||
|
body = AcsFault(rpc.acsFault);
|
||||||
}
|
}
|
||||||
|
|
||||||
headers["Content-Type"] = 'text/xml; charset="utf-8"';
|
headers["Content-Type"] = 'text/xml; charset="utf-8"';
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ export interface SoapMessage {
|
|||||||
cpeRequest?: CpeRequest;
|
cpeRequest?: CpeRequest;
|
||||||
cpeFault?: CpeFault;
|
cpeFault?: CpeFault;
|
||||||
cpeResponse?: CpeResponse;
|
cpeResponse?: CpeResponse;
|
||||||
|
unknownMethod?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptResult {
|
export interface ScriptResult {
|
||||||
|
|||||||
+4
-4
@@ -17,7 +17,7 @@
|
|||||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as stream from "stream";
|
import { PassThrough } from "stream";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import * as db from "./db";
|
import * as db from "./db";
|
||||||
import * as apiFunctions from "./api-functions";
|
import * as apiFunctions from "./api-functions";
|
||||||
@@ -103,7 +103,7 @@ router.get(`/devices/:id.csv`, async (ctx) => {
|
|||||||
.replace(/[:.]/g, "")}.csv`
|
.replace(/[:.]/g, "")}.csv`
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = new stream.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"
|
||||||
);
|
);
|
||||||
@@ -216,7 +216,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = new stream.PassThrough();
|
ctx.body = new PassThrough();
|
||||||
ctx.type = "application/json";
|
ctx.type = "application/json";
|
||||||
|
|
||||||
let c = 0;
|
let c = 0;
|
||||||
@@ -276,7 +276,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = new stream.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`
|
||||||
|
|||||||
Generated
+9100
-3006
File diff suppressed because it is too large.
Load diff
+20
-20
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.7",
|
"version": "1.2.8",
|
||||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -19,55 +19,55 @@
|
|||||||
"bin": "bin"
|
"bin": "bin"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@breejs/later": "^4.1.0",
|
||||||
"espresso-iisojs": "^1.0.4",
|
"espresso-iisojs": "^1.0.4",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"koa": "^2.13.1",
|
"koa": "^2.13.4",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
"koa-compress": "^5.1.0",
|
"koa-compress": "^5.1.0",
|
||||||
"koa-jwt": "^4.0.1",
|
"koa-jwt": "^4.0.3",
|
||||||
"koa-router": "^10.1.1",
|
"koa-router": "^10.1.1",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"later": "^1.2.0",
|
"mongodb": "^4.1.3",
|
||||||
"mongodb": "^4.1.2",
|
|
||||||
"parsimmon": "^1.18.0",
|
"parsimmon": "^1.18.0",
|
||||||
"seedrandom": "^3.0.5"
|
"seedrandom": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^20.0.0",
|
"@rollup/plugin-commonjs": "^21.0.1",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.0.4",
|
"@rollup/plugin-node-resolve": "^13.0.6",
|
||||||
"@rollup/plugin-typescript": "^8.2.5",
|
"@rollup/plugin-typescript": "^8.3.0",
|
||||||
"@types/codemirror": "^5.60.2",
|
"@types/codemirror": "^5.60.5",
|
||||||
"@types/jsonwebtoken": "^8.5.5",
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
"@types/koa": "^2.13.4",
|
"@types/koa": "^2.13.4",
|
||||||
"@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.8",
|
"@types/mithril": "^2.0.8",
|
||||||
"@types/node": "^16.9.2",
|
"@types/node": "^16.11.6",
|
||||||
"@types/parsimmon": "^1.10.6",
|
"@types/parsimmon": "^1.10.6",
|
||||||
"@types/seedrandom": "^3.0.1",
|
"@types/seedrandom": "^3.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
||||||
"@typescript-eslint/parser": "^4.31.1",
|
"@typescript-eslint/parser": "^5.2.0",
|
||||||
"ava": "^3.15.0",
|
"ava": "^3.15.0",
|
||||||
"codemirror": "^5.62.3",
|
"codemirror": "^5.63.3",
|
||||||
"cssnano": "^5.0.8",
|
"cssnano": "^5.0.8",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^8.1.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"mithril": "^2.0.4",
|
"mithril": "^2.0.4",
|
||||||
"postcss": "^8.3.6",
|
"postcss": "^8.3.11",
|
||||||
"postcss-import": "^14.0.2",
|
"postcss-import": "^14.0.2",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.4.1",
|
"prettier": "^2.4.1",
|
||||||
"rollup": "^2.56.3",
|
"rollup": "^2.58.3",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"sql.js": "^1.6.1",
|
"sql.js": "^1.6.2",
|
||||||
"svgo": "^2.6.1",
|
"svgo": "^2.7.0",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.4.3",
|
"typescript": "^4.4.4",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ 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";
|
||||||
|
|
||||||
const memoizedParse = memoize(parse);
|
const memoizedParse = memoize(parse);
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
|
|
||||||
const limit = vnode.attrs["limit"] > 0 ? vnode.attrs["limit"] : 100;
|
const limit = evaluateExpression(vnode.attrs["limit"], device) || 100;
|
||||||
|
|
||||||
const search = m("input", {
|
const search = m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
+59
-12
@@ -17,29 +17,76 @@
|
|||||||
* 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 { Attributes, ClosureComponent, Component } from "mithril";
|
||||||
|
import { map } from "../../lib/common/expression-parser";
|
||||||
|
import memoize from "../../lib/common/memoize";
|
||||||
|
import { Expression } from "../../lib/types";
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as store from "../store";
|
import { evaluateExpression, getTimestamp } from "../store";
|
||||||
|
|
||||||
|
const evaluateAttributes = memoize(
|
||||||
|
(
|
||||||
|
attrs: Record<string, Expression>,
|
||||||
|
obj: Record<string, unknown>,
|
||||||
|
now: number // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
): Attributes => {
|
||||||
|
const res: Attributes = {};
|
||||||
|
for (const [k, v] of Object.entries(attrs)) {
|
||||||
|
const vv = map(v, (e) => {
|
||||||
|
if (
|
||||||
|
Array.isArray(e) &&
|
||||||
|
e[0] === "FUNC" &&
|
||||||
|
e[1] === "ENCODEURICOMPONENT"
|
||||||
|
) {
|
||||||
|
const a = evaluateExpression(e[2], obj);
|
||||||
|
if (a == null) return null;
|
||||||
|
return encodeURIComponent(a as string);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
res[k] = evaluateExpression(vv, obj);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
if (vnode.attrs["filter"]) {
|
const device = vnode.attrs["device"];
|
||||||
if (
|
if ("filter" in vnode.attrs) {
|
||||||
!store.evaluateExpression(
|
if (!evaluateExpression(vnode.attrs["filter"], device || {}))
|
||||||
vnode.attrs["filter"],
|
|
||||||
vnode.attrs["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 (typeof c !== "object") return `${c}`;
|
if (typeof c !== "object") return `${c}`;
|
||||||
return m(c["type"], c);
|
const type = evaluateExpression(c["type"], device || {});
|
||||||
|
if (!type) return null;
|
||||||
|
return m(type as string, c);
|
||||||
});
|
});
|
||||||
if (vnode.attrs["element"]) return m(vnode.attrs["element"], children);
|
|
||||||
else return children;
|
let el = vnode.attrs["element"];
|
||||||
|
|
||||||
|
if (el == null) return children;
|
||||||
|
|
||||||
|
let attrs: Attributes;
|
||||||
|
if (Array.isArray(el)) {
|
||||||
|
el = evaluateExpression(el, device || {});
|
||||||
|
} else if (typeof el === "object") {
|
||||||
|
if (el["attributes"] != null) {
|
||||||
|
attrs = evaluateAttributes(
|
||||||
|
el["attributes"],
|
||||||
|
device || {},
|
||||||
|
getTimestamp()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
el = evaluateExpression(el["tag"], device || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return m(el, attrs, children);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,18 +19,22 @@
|
|||||||
|
|
||||||
import { ClosureComponent, Component } from "mithril";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
|
import { evaluateExpression } from "../store";
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
let deviceId;
|
let deviceId;
|
||||||
if (vnode.attrs["device"])
|
const device = vnode.attrs["device"];
|
||||||
deviceId = vnode.attrs["device"]["DeviceID.ID"].value[0];
|
if (device) deviceId = device["DeviceID.ID"].value[0];
|
||||||
|
|
||||||
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 (typeof c !== "object") return `${c}`;
|
if (typeof c !== "object") return `${c}`;
|
||||||
|
const type = evaluateExpression(c["type"], device || {});
|
||||||
|
if (!type) return null;
|
||||||
const attrs = Object.assign({}, vnode.attrs, c);
|
const attrs = Object.assign({}, vnode.attrs, c);
|
||||||
return m(attrs["type"], attrs);
|
return m(type as string, attrs);
|
||||||
});
|
});
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
return m(
|
return m(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { ClosureComponent, Component } from "mithril";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import * as store from "../store";
|
import { evaluateExpression } from "../store";
|
||||||
|
|
||||||
const CHARTS = config.ui.overview.charts;
|
const CHARTS = config.ui.overview.charts;
|
||||||
|
|
||||||
@@ -28,11 +28,12 @@ const component: ClosureComponent = (): Component => {
|
|||||||
return {
|
return {
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
const chart = CHARTS[vnode.attrs["chart"]] as Record<string, unknown>;
|
const chartName = evaluateExpression(vnode.attrs["chart"], device || {});
|
||||||
|
const chart = CHARTS[chartName as string] as Record<string, unknown>;
|
||||||
if (!chart) return null;
|
if (!chart) return null;
|
||||||
for (const slice of Object.values(chart.slices)) {
|
for (const slice of Object.values(chart.slices)) {
|
||||||
const filter = slice["filter"];
|
const filter = slice["filter"];
|
||||||
if (store.evaluateExpression(filter, device)) {
|
if (evaluateExpression(filter, device || {})) {
|
||||||
const dot = m(
|
const dot = m(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
import { ClosureComponent, Component, VnodeDOM } from "mithril";
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
|
import { evaluateExpression } from "../store";
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
@@ -26,12 +27,13 @@ const component: ClosureComponent = (): Component => {
|
|||||||
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 p = m.context(
|
const p = m.context(
|
||||||
{
|
{
|
||||||
device: device,
|
device: device,
|
||||||
parameter: parameter["parameter"],
|
parameter: parameter["parameter"],
|
||||||
},
|
},
|
||||||
parameter["type"] || "parameter",
|
(type as string) || "parameter",
|
||||||
parameter
|
parameter
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { ClosureComponent, Component } from "mithril";
|
import { ClosureComponent, Component } from "mithril";
|
||||||
import { m } from "../components";
|
import { m } from "../components";
|
||||||
import * as taskQueue from "../task-queue";
|
import * as taskQueue from "../task-queue";
|
||||||
import * as store from "../store";
|
import { 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";
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
const object = store.evaluateExpression(vnode.state["object"], device);
|
const object = evaluateExpression(vnode.state["object"], device);
|
||||||
const parameters = vnode.state["parameters"];
|
const parameters = vnode.state["parameters"];
|
||||||
|
|
||||||
if (typeof object !== "string" || !device[object]) return null;
|
if (typeof object !== "string" || !device[object]) return null;
|
||||||
@@ -56,8 +56,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (const i of instances) {
|
for (const i of instances) {
|
||||||
let filter =
|
let filter = "filter" in vnode.attrs ? vnode.attrs["filter"] : true;
|
||||||
vnode.attrs["filter"] != null ? vnode.attrs["filter"] : true;
|
|
||||||
|
|
||||||
filter = expressionParser.map(filter, (e) => {
|
filter = expressionParser.map(filter, (e) => {
|
||||||
if (Array.isArray(e) && e[0] === "PARAM")
|
if (Array.isArray(e) && e[0] === "PARAM")
|
||||||
@@ -65,7 +64,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!store.evaluateExpression(filter, device)) continue;
|
if (!evaluateExpression(filter, device)) continue;
|
||||||
|
|
||||||
const row = parameters.map((p) => {
|
const row = parameters.map((p) => {
|
||||||
const param = expressionParser.map(p.parameter, (e) => {
|
const param = expressionParser.map(p.parameter, (e) => {
|
||||||
@@ -147,7 +146,9 @@ const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let label;
|
let label;
|
||||||
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
|
||||||
|
const l = evaluateExpression(vnode.attrs["label"], device);
|
||||||
|
if (l != null) label = m("h2", l);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
label,
|
label,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const { value, timestamp, parameter } = evaluateParam(
|
const { value, timestamp, parameter } = evaluateParam(
|
||||||
vnode.attrs["parameter"],
|
vnode.attrs["parameter"],
|
||||||
vnode.attrs["device"],
|
device,
|
||||||
store.getTimestamp() + store.getClockSkew()
|
store.getTimestamp() + store.getClockSkew()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ const component: ClosureComponent = (): Component => {
|
|||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
const device = vnode.attrs["device"];
|
const device = vnode.attrs["device"];
|
||||||
let writable = true;
|
let writable = true;
|
||||||
if (vnode.attrs["writable"] != null) writable = vnode.attrs["writable"];
|
if ("writable" in vnode.attrs)
|
||||||
|
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))
|
||||||
|
|||||||
+4
-3
@@ -268,17 +268,18 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sortedAttrs): void {
|
function onSortChange(sortedAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortedAttrs)) {
|
for (const [index, direction] of Object.entries(sortedAttrs)) {
|
||||||
const param = memoizedGetSortable(attributes[index].parameter);
|
const param = memoizedGetSortable(attributes[index].parameter);
|
||||||
if (param) {
|
if (param) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[param];
|
delete _sort[param];
|
||||||
_sort[param] = direction;
|
if (direction) _sort = Object.assign({ [param]: direction }, _sort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/devices", ops);
|
m.route.set("/devices", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -107,14 +107,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/faults", ops);
|
m.route.set("/faults", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -151,14 +151,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
sortAttributes[i] = sort[attributes[i].id] || 0;
|
sortAttributes[i] = sort[attributes[i].id] || 0;
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/files", ops);
|
m.route.set("/admin/files", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ function renderTable(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = 1;
|
|
||||||
|
|
||||||
let symbol = getIcon("unsorted");
|
let symbol = getIcon("unsorted");
|
||||||
if (sortAttributes[i] > 0) symbol = getIcon("sorted-asc");
|
if (sortAttributes[i] > 0) symbol = getIcon("sorted-asc");
|
||||||
else if (sortAttributes[i] < 0) symbol = getIcon("sorted-dsc");
|
else if (sortAttributes[i] < 0) symbol = getIcon("sorted-dsc");
|
||||||
@@ -89,7 +87,7 @@ function renderTable(
|
|||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
if (sortAttributes[i] > 0) direction *= -1;
|
const direction = ((sortAttributes[i] + 2) % 3) - 1;
|
||||||
return onSortChange({ [i]: direction });
|
return onSortChange({ [i]: direction });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -205,14 +205,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/permissions", ops);
|
m.route.set("/admin/permissions", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -190,14 +190,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/presets", ops);
|
m.route.set("/admin/presets", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,14 +173,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
sortAttributes[i] = sort[attributes[i].id] || 0;
|
sortAttributes[i] = sort[attributes[i].id] || 0;
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/provisions", ops);
|
m.route.set("/admin/provisions", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -186,14 +186,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/users", ops);
|
m.route.set("/admin/users", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,14 +173,16 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
sortAttributes[i] = sort[attributes[i].id] || 0;
|
sortAttributes[i] = sort[attributes[i].id] || 0;
|
||||||
|
|
||||||
function onSortChange(sortAttrs): void {
|
function onSortChange(sortAttrs): void {
|
||||||
const _sort = Object.assign({}, sort);
|
let _sort = Object.assign({}, sort);
|
||||||
for (const [index, direction] of Object.entries(sortAttrs)) {
|
for (const [index, direction] of Object.entries(sortAttrs)) {
|
||||||
// Changing the priority of columns
|
// Changing the priority of columns
|
||||||
delete _sort[attributes[index].id];
|
delete _sort[attributes[index].id];
|
||||||
_sort[attributes[index].id] = direction;
|
if (direction)
|
||||||
|
_sort = Object.assign({ [attributes[index].id]: direction }, _sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ops = { sort: JSON.stringify(_sort) };
|
const ops = {};
|
||||||
|
if (Object.keys(_sort).length) ops["sort"] = JSON.stringify(_sort);
|
||||||
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
if (vnode.attrs["filter"]) ops["filter"] = vnode.attrs["filter"];
|
||||||
m.route.set("/admin/virtualParameters", ops);
|
m.route.set("/admin/virtualParameters", ops);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user