Support Unicode characters in tags
This commit is contained in:
1 parent
dfab724515
commit
c52ff6f6ec
7 files changed
+48
-40
No files matched your search
@@ -47,3 +47,17 @@ export function generateDeviceId(
|
||||
export function escapeRegExp(str: string): string {
|
||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||
}
|
||||
|
||||
export function encodeTag(tag: string): string {
|
||||
return encodeURIComponent(tag)
|
||||
.replace(
|
||||
/[!~*'()]/g,
|
||||
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
)
|
||||
.replace(/0x(?=[0-9A-Z]{2})/g, "0%78")
|
||||
.replace(/%/g, "0x");
|
||||
}
|
||||
|
||||
export function decodeTag(tag: string): string {
|
||||
return decodeURIComponent(tag.replace(/0x(?=[0-9A-Z]{2})/g, "%"));
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { MongoClient, ObjectID, Collection } from "mongodb";
|
||||
import { get } from "./config";
|
||||
import { escapeRegExp } from "./common";
|
||||
import { encodeTag, escapeRegExp } from "./common";
|
||||
import { parse } from "./common/expression-parser";
|
||||
import {
|
||||
DeviceData,
|
||||
@@ -234,10 +234,9 @@ export async function fetchDevice(
|
||||
]);
|
||||
}
|
||||
|
||||
for (let t of v as string[]) {
|
||||
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
|
||||
for (const t of v as string[]) {
|
||||
res.push([
|
||||
Path.parse("Tags." + t),
|
||||
Path.parse("Tags." + encodeTag(t)),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ObjectID } from "mongodb";
|
||||
import { map, parse, stringify, parseList } from "./common/expression-parser";
|
||||
import { likePatternToRegExp } from "./common/expression";
|
||||
import { Expression, Fault, Task } from "./types";
|
||||
import { decodeTag, encodeTag } from "./common";
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
@@ -43,7 +44,7 @@ export function processDeviceFilter(filter: Expression): Expression {
|
||||
exp[1][0] === "PARAM" &&
|
||||
exp[1][1].startsWith("Tags.")
|
||||
) {
|
||||
const t = exp[1][1].slice(5);
|
||||
const t = decodeTag(exp[1][1].slice(5));
|
||||
if (exp[0] === "IS NULL") return ["_tags", t, false];
|
||||
else if (exp[0] === "IS NOT NULL") return ["_tags", t, true];
|
||||
else if (exp[0] === "=" && exp[2] === true) return ["_tags", t, true];
|
||||
@@ -406,7 +407,7 @@ export function flattenDevice(device: Record<string, unknown>): FlatDevice {
|
||||
};
|
||||
|
||||
for (const t of tree as string[]) {
|
||||
output[`Tags.${t}`] = {
|
||||
output[`Tags.${encodeTag(t)}`] = {
|
||||
value: [true, "xsd:boolean"],
|
||||
valueTimestamp: timestamp,
|
||||
writable: true,
|
||||
@@ -510,11 +511,11 @@ export function convertOldPrecondition(
|
||||
if (op === "$ne") {
|
||||
if (typeof v["$ne"] !== "string")
|
||||
throw new Error("Only string values are allowed for _tags");
|
||||
conjs.push(["IS NULL", ["PARAM", `Tags.${val}`]]);
|
||||
conjs.push(["IS NULL", ["PARAM", `Tags.${encodeTag(val)}`]]);
|
||||
} else if (op === "$eq") {
|
||||
if (typeof v["$eq"] !== "string")
|
||||
throw new Error("Only string values are allowed for _tags");
|
||||
conjs.push(["IS NOT NULL", ["PARAM", `Tags.${val}`]]);
|
||||
conjs.push(["IS NOT NULL", ["PARAM", `Tags.${encodeTag(val)}`]]);
|
||||
} else {
|
||||
throw new Error(`Invalid tag query`);
|
||||
}
|
||||
@@ -522,7 +523,10 @@ export function convertOldPrecondition(
|
||||
if (conjs.length === 1) expressions.push(conjs[0]);
|
||||
else if (conjs.length > 1) expressions.push(["AND", ...conjs]);
|
||||
} else {
|
||||
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]);
|
||||
expressions.push([
|
||||
"IS NOT NULL",
|
||||
["PARAM", `Tags.${encodeTag(v as string)}`],
|
||||
]);
|
||||
}
|
||||
} else if (k.startsWith("Tags.")) {
|
||||
let exists: boolean;
|
||||
|
||||
+2
-1
@@ -31,6 +31,7 @@ import { del } from "../cache";
|
||||
import Authorizer from "../common/authorizer";
|
||||
import { ping } from "../ping";
|
||||
import * as url from "url";
|
||||
import { decodeTag } from "../common";
|
||||
|
||||
const router = new Router();
|
||||
export default router;
|
||||
@@ -280,7 +281,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
||||
if (e[1] === "Tags") {
|
||||
const tags = [];
|
||||
for (const p in obj)
|
||||
if (p.startsWith("Tags.")) tags.push(p.slice(5));
|
||||
if (p.startsWith("Tags.")) tags.push(decodeTag(p.slice(5)));
|
||||
|
||||
return tags.join(", ");
|
||||
}
|
||||
|
||||
+16
-28
@@ -237,39 +237,27 @@ export function count(resource: string, filter: Expression): Promise<number> {
|
||||
});
|
||||
}
|
||||
|
||||
export function updateDeviceTags(
|
||||
export async function updateDeviceTags(
|
||||
deviceId: string,
|
||||
tags: Record<string, boolean>
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const add = [];
|
||||
const pull = [];
|
||||
const add = [];
|
||||
const pull = [];
|
||||
|
||||
const regex = /^[0-9a-zA-Z_]+$/;
|
||||
for (let [tag, onOff] of Object.entries(tags)) {
|
||||
tag = tag.trim();
|
||||
if (onOff) {
|
||||
if (!tag.match(regex))
|
||||
return void reject(new Error(`Invalid tag '${tag}'`));
|
||||
add.push(tag);
|
||||
} else {
|
||||
pull.push(tag);
|
||||
}
|
||||
}
|
||||
getClient()
|
||||
.then((client) => {
|
||||
const collection = client.db().collection("devices");
|
||||
for (let [tag, onOff] of Object.entries(tags)) {
|
||||
tag = tag.trim();
|
||||
if (onOff) add.push(tag);
|
||||
else pull.push(tag);
|
||||
}
|
||||
|
||||
const object = {};
|
||||
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
||||
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
||||
collection.updateOne({ _id: deviceId }, object, (err) => {
|
||||
if (err) return void reject(err);
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
const client = await getClient();
|
||||
const collection = client.db().collection("devices");
|
||||
const object = {};
|
||||
|
||||
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
||||
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
||||
|
||||
await collection.updateOne({ _id: deviceId }, object);
|
||||
}
|
||||
|
||||
function putResource(resource, id, object): Promise<void> {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { m } from "../components";
|
||||
import * as notifications from "../notifications";
|
||||
import * as store from "../store";
|
||||
import { getIcon } from "../icons";
|
||||
import { decodeTag } from "../../lib/common";
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
return {
|
||||
@@ -32,7 +33,7 @@ const component: ClosureComponent = (): Component => {
|
||||
|
||||
const tags = [];
|
||||
for (const p of Object.keys(device))
|
||||
if (p.startsWith("Tags.")) tags.push(p.slice(5));
|
||||
if (p.startsWith("Tags.")) tags.push(decodeTag(p.slice(5)));
|
||||
|
||||
tags.sort();
|
||||
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@
|
||||
|
||||
import config from "./config";
|
||||
import { Expression } from "../lib/types";
|
||||
import { encodeTag } from "../lib/common";
|
||||
|
||||
const resources = {
|
||||
devices: {},
|
||||
@@ -138,7 +139,7 @@ function queryMac(param, value): Expression {
|
||||
}
|
||||
|
||||
function queryTag(tag: string): Expression {
|
||||
const t = tag.replace(/[^a-zA-Z0-9-]+/g, "_");
|
||||
const t = encodeTag(tag);
|
||||
return ["IS NOT NULL", ["PARAM", `Tags.${t}`]];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user