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 {
|
export function escapeRegExp(str: string): string {
|
||||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
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 { MongoClient, ObjectID, Collection } from "mongodb";
|
||||||
import { get } from "./config";
|
import { get } from "./config";
|
||||||
import { escapeRegExp } from "./common";
|
import { encodeTag, escapeRegExp } from "./common";
|
||||||
import { parse } from "./common/expression-parser";
|
import { parse } from "./common/expression-parser";
|
||||||
import {
|
import {
|
||||||
DeviceData,
|
DeviceData,
|
||||||
@@ -234,10 +234,9 @@ export async function fetchDevice(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let t of v as string[]) {
|
for (const t of v as string[]) {
|
||||||
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
||||||
res.push([
|
res.push([
|
||||||
Path.parse("Tags." + t),
|
Path.parse("Tags." + encodeTag(t)),
|
||||||
timestamp,
|
timestamp,
|
||||||
{
|
{
|
||||||
object: [timestamp, 0],
|
object: [timestamp, 0],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { ObjectID } from "mongodb";
|
|||||||
import { map, parse, stringify, parseList } from "./common/expression-parser";
|
import { map, parse, stringify, parseList } from "./common/expression-parser";
|
||||||
import { likePatternToRegExp } from "./common/expression";
|
import { likePatternToRegExp } from "./common/expression";
|
||||||
import { Expression, Fault, Task } from "./types";
|
import { Expression, Fault, Task } from "./types";
|
||||||
|
import { decodeTag, encodeTag } from "./common";
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ export function processDeviceFilter(filter: Expression): Expression {
|
|||||||
exp[1][0] === "PARAM" &&
|
exp[1][0] === "PARAM" &&
|
||||||
exp[1][1].startsWith("Tags.")
|
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];
|
if (exp[0] === "IS NULL") return ["_tags", t, false];
|
||||||
else if (exp[0] === "IS NOT NULL") return ["_tags", t, true];
|
else if (exp[0] === "IS NOT NULL") return ["_tags", t, true];
|
||||||
else if (exp[0] === "=" && exp[2] === true) 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[]) {
|
for (const t of tree as string[]) {
|
||||||
output[`Tags.${t}`] = {
|
output[`Tags.${encodeTag(t)}`] = {
|
||||||
value: [true, "xsd:boolean"],
|
value: [true, "xsd:boolean"],
|
||||||
valueTimestamp: timestamp,
|
valueTimestamp: timestamp,
|
||||||
writable: true,
|
writable: true,
|
||||||
@@ -510,11 +511,11 @@ export function convertOldPrecondition(
|
|||||||
if (op === "$ne") {
|
if (op === "$ne") {
|
||||||
if (typeof v["$ne"] !== "string")
|
if (typeof v["$ne"] !== "string")
|
||||||
throw new Error("Only string values are allowed for _tags");
|
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") {
|
} else if (op === "$eq") {
|
||||||
if (typeof v["$eq"] !== "string")
|
if (typeof v["$eq"] !== "string")
|
||||||
throw new Error("Only string values are allowed for _tags");
|
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 {
|
} else {
|
||||||
throw new Error(`Invalid tag query`);
|
throw new Error(`Invalid tag query`);
|
||||||
}
|
}
|
||||||
@@ -522,7 +523,10 @@ export function convertOldPrecondition(
|
|||||||
if (conjs.length === 1) expressions.push(conjs[0]);
|
if (conjs.length === 1) expressions.push(conjs[0]);
|
||||||
else if (conjs.length > 1) expressions.push(["AND", ...conjs]);
|
else if (conjs.length > 1) expressions.push(["AND", ...conjs]);
|
||||||
} else {
|
} 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.")) {
|
} else if (k.startsWith("Tags.")) {
|
||||||
let exists: boolean;
|
let exists: boolean;
|
||||||
|
|||||||
+2
-1
@@ -31,6 +31,7 @@ import { del } from "../cache";
|
|||||||
import Authorizer from "../common/authorizer";
|
import Authorizer from "../common/authorizer";
|
||||||
import { ping } from "../ping";
|
import { ping } from "../ping";
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
|
import { decodeTag } from "../common";
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
export default router;
|
export default router;
|
||||||
@@ -280,7 +281,7 @@ for (const [resource, flags] of Object.entries(resources)) {
|
|||||||
if (e[1] === "Tags") {
|
if (e[1] === "Tags") {
|
||||||
const tags = [];
|
const tags = [];
|
||||||
for (const p in obj)
|
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(", ");
|
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,
|
deviceId: string,
|
||||||
tags: Record<string, boolean>
|
tags: Record<string, boolean>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
const add = [];
|
||||||
const add = [];
|
const pull = [];
|
||||||
const pull = [];
|
|
||||||
|
|
||||||
const regex = /^[0-9a-zA-Z_]+$/;
|
for (let [tag, onOff] of Object.entries(tags)) {
|
||||||
for (let [tag, onOff] of Object.entries(tags)) {
|
tag = tag.trim();
|
||||||
tag = tag.trim();
|
if (onOff) add.push(tag);
|
||||||
if (onOff) {
|
else pull.push(tag);
|
||||||
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");
|
|
||||||
|
|
||||||
const object = {};
|
const client = await getClient();
|
||||||
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
const collection = client.db().collection("devices");
|
||||||
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
const object = {};
|
||||||
collection.updateOne({ _id: deviceId }, object, (err) => {
|
|
||||||
if (err) return void reject(err);
|
if (add && add.length) object["$addToSet"] = { _tags: { $each: add } };
|
||||||
resolve();
|
if (pull && pull.length) object["$pullAll"] = { _tags: pull };
|
||||||
});
|
|
||||||
})
|
await collection.updateOne({ _id: deviceId }, object);
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function putResource(resource, id, object): Promise<void> {
|
function putResource(resource, id, object): Promise<void> {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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/common";
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
return {
|
return {
|
||||||
@@ -32,7 +33,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
|
|
||||||
const tags = [];
|
const tags = [];
|
||||||
for (const p of Object.keys(device))
|
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();
|
tags.sort();
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import { Expression } from "../lib/types";
|
import { Expression } from "../lib/types";
|
||||||
|
import { encodeTag } from "../lib/common";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
devices: {},
|
devices: {},
|
||||||
@@ -138,7 +139,7 @@ function queryMac(param, value): Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function queryTag(tag: string): 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}`]];
|
return ["IS NOT NULL", ["PARAM", `Tags.${t}`]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user